<template>
  <div class="video-converter">
    <div class="container">
      <!-- 标题 -->
      <div class="header">
        <h1>
          <img src="https://free-1325650170.cos.ap-guangzhou.myqcloud.com/free_convert/freeconvert-logo.svg" alt="FreeConvert Logo" class="logo-icon"/>
          视频转GIF在线工具
        </h1>
        <p class="subtitle">快速将视频转换为GIF动图，支持自定义尺寸和帧率</p>
      </div>

      <!-- 上传与预览区域（桌面端更宽，更适配） -->
      <div class="upload-section">
        <div v-if="!selectedFile && !isUploading"
          class="upload-area" 
          :class="{ 'drag-over': isDragOver }"
          @drop.prevent="handleDrop"
          @dragover.prevent="isDragOver = true"
          @dragleave.prevent="isDragOver = false"
        >
          <input 
            type="file" 
            ref="fileInput" 
            accept="video/*"
            @change="handleFileSelect"
            style="display: none"
          />
          
          <div class="upload-placeholder">
            <div class="upload-icon">📁</div>
            <p class="upload-text">点击选择或拖拽视频文件到这里</p>
            <p class="upload-hint">支持 MP4、AVI、MOV 等格式，最大100MB</p>
            <button class="select-btn" @click="$refs.fileInput.click()">选择文件</button>
          </div>
        </div>

        <!-- 工作台：左视频 / 右参数 -->
        <div v-if="videoUrl" class="workbench">
          <!-- 左侧：视频与时间轴 -->
          <section class="preview-section">
          <!-- <div class="video-header">
            <button class="change-file-btn" @click="$refs.fileInput.click()">
              更换文件
            </button>
          </div> -->
          
            <div class="video-preview-wrapper">
          <video 
            ref="videoPlayer"
            :src="videoUrl" 
            @loadedmetadata="handleVideoLoaded"
            @timeupdate="handleVideoTimeUpdate"
            @play="handleVideoPlay"
            @pause="handleVideoPause"
            @ended="handleVideoEnded"
            @waiting="handleVideoWaiting"
            @stalled="handleVideoStalled"
            @canplay="handleVideoCanPlay"
            @canplaythrough="handleVideoCanPlayThrough"
            @click="handleVideoClick"
            @mousedown="handleVideoMouseDown"
            @touchstart="handleVideoTouchStart"
            @touchend="handleVideoTouchEnd"
            autoplay
            muted
            preload="auto"
            playsinline
            class="video-player"
            :style="videoPlayerStyle"
          ></video>
          
              <!-- 裁剪遮罩层 -->
              <div v-if="isCropMode" class="crop-overlay">
                <div 
                  class="crop-area"
                  :style="cropAreaStyle"
                  :class="{ 'crop-disabled': isUploading }"
                  @mousedown="handleCropAreaMouseDown"
                  @touchstart="handleCropAreaTouchStart"
                >
                  <!-- 裁剪框四个角的控制点 -->
                  <div class="crop-handle crop-handle-tl" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('tl', $event)" @touchstart.stop="handleCropHandleTouchStart('tl', $event)"></div>
                  <div class="crop-handle crop-handle-tr" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('tr', $event)" @touchstart.stop="handleCropHandleTouchStart('tr', $event)"></div>
                  <div class="crop-handle crop-handle-bl" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('bl', $event)" @touchstart.stop="handleCropHandleTouchStart('bl', $event)"></div>
                  <div class="crop-handle crop-handle-br" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('br', $event)" @touchstart.stop="handleCropHandleTouchStart('br', $event)"></div>
                  
                  <!-- 裁剪框四边的控制点 -->
                  <div class="crop-handle crop-handle-t" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('t', $event)" @touchstart.stop="handleCropHandleTouchStart('t', $event)"></div>
                  <div class="crop-handle crop-handle-r" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('r', $event)" @touchstart.stop="handleCropHandleTouchStart('r', $event)"></div>
                  <div class="crop-handle crop-handle-b" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('b', $event)" @touchstart.stop="handleCropHandleTouchStart('b', $event)"></div>
                  <div class="crop-handle crop-handle-l" :class="{ 'crop-handle-disabled': isUploading }" @mousedown.stop="handleCropHandleMouseDown('l', $event)" @touchstart.stop="handleCropHandleTouchStart('l', $event)"></div>
                </div>
              </div>
            </div>
            
            <!-- 裁剪控制按钮 -->
            <div class="crop-controls">
              <button 
                v-if="!isCropMode" 
                @click="enableCropMode" 
                class="crop-btn"
                :disabled="isUploading"
              >
                启动裁剪
              </button>
              <div v-else class="crop-actions">
                <button @click="applyCrop" class="crop-btn primary" :disabled="isUploading">✓ 应用裁剪</button>
                <button @click="cancelCrop" class="crop-btn secondary" :disabled="isUploading">✗ 取消</button>
                <button @click="resetCrop" class="crop-btn secondary" :disabled="isUploading">🔄 重置</button>
              </div>
            </div>
            
          <div class="trimmer-section">
            <div class="trimmer-container">
              <div class="trimmer-track" ref="trimmerTrack" @mousedown="handleTrackClick" @touchstart="handleTrackTouchStart">
                <!-- 加载提示 -->
                <div v-if="isGeneratingThumbnails" class="trimmer-loading">
                  <div class="loading-spinner"></div>
                  <p>正在生成视频帧预览...</p>
                </div>
                
                <!-- 视频缩略图预览带 -->
                <div v-else class="trimmer-thumbnails">
                  <div 
                    v-for="(thumbnail, index) in videoThumbnails" 
                    :key="index"
                    class="trimmer-thumbnail"
                    :style="{ 
                      left: thumbnail.position + '%',
                      width: thumbnail.width + '%',
                      backgroundImage: `url(${thumbnail.url})`
                    }"
                  >
                    <div class="thumbnail-time">{{ formatTime(thumbnail.time) }}</div>
                  </div>
                </div>
                
                <!-- 两端加厚的可拖拽区域（便于手机端触发） -->
                <div 
                  class="trimmer-edge-gutter trimmer-edge-gutter-left"
                  @mousedown.stop="handleEdgeGutterMouseDown('start', $event)"
                  @touchstart.stop="handleEdgeGutterTouchStart('start', $event)"
                ></div>
                <div 
                  class="trimmer-edge-gutter trimmer-edge-gutter-right"
                  @mousedown.stop="handleEdgeGutterMouseDown('end', $event)"
                  @touchstart.stop="handleEdgeGutterTouchStart('end', $event)"
                ></div>
                
                <!-- 选择区域 -->
                <div 
                  class="trimmer-selection"
                    :class="{ 'trimmer-disabled': isUploading }"
                    :style="{
                      left: (trimStart / videoDuration * 100) + '%',
                      width: ((trimEnd - trimStart) / videoDuration * 100) + '%'
                    }"
                    @mousedown.stop="handleSelectionMouseDown"
                    @touchstart.stop="handleSelectionTouchStart"
                  >
                  <!-- 左侧手柄 -->
                  <div 
                    class="trimmer-handle trimmer-handle-left"
                    :class="{ 'trimmer-handle-disabled': isUploading }"
                    @mousedown.stop="handleHandleMouseDown('start', $event)"
                    @touchstart.stop="handleHandleTouchStart('start', $event)"
                  >
                    <div class="trimmer-handle-icon"></div>
                  </div>
                  
                  <!-- 右侧手柄 -->
                  <div 
                    class="trimmer-handle trimmer-handle-right"
                    :class="{ 'trimmer-handle-disabled': isUploading }"
                    @mousedown.stop="handleHandleMouseDown('end', $event)"
                    @touchstart.stop="handleHandleTouchStart('end', $event)"
                  >
                    <div class="trimmer-handle-icon"></div>
                  </div>
                </div>
              </div>
            </div>
            
            <!-- 时间显示 -->
            <div class="trimmer-info">
              <div class="trimmer-time">
                <span class="time-label">开始:</span>
                <span class="time-value">{{ formatTime(trimStart) }}</span>
              </div>
              <div class="trimmer-time">
                <span class="time-label">时长:</span>
                <span class="time-value">{{ formatTime(trimEnd - trimStart) }}</span>
              </div>
              <div class="trimmer-time">
                <span class="time-label">结束:</span>
                <span class="time-value">{{ formatTime(trimEnd) }}</span>
              </div>
              <div class="trimmer-time">
                <span class="time-label">帧数:</span>
                <span class="time-value">{{ estimatedFrames }} 帧</span>
              </div>
            </div>
            
            <!-- 播放控制按钮 -->
            <div class="trimmer-controls">
              <button @click="playTrimmedSection" class="control-btn" :disabled="isUploading">
                {{ isPlaying ? '⏸️ 暂停' : '▶️ 预览片段' }}
              </button>
              <button @click="resetTrim" class="control-btn secondary" :disabled="isUploading">
                🔄 重置裁剪
              </button>
            </div>
          </div>
          </section>

          <!-- 右侧：转换参数 -->
          <aside class="options-card">
          <h3>转换参数</h3>
          
            <!-- 裁剪参数（仅在启用裁剪时显示） -->
            <div v-if="cropOptions.enabled" class="crop-params">
              <h4>✂️ 裁剪设置</h4>
              <div class="option-row">
                <div class="option-item">
                  <label>裁剪X (px)</label>
                  <input v-model.number="cropOptions.x" type="number" min="0" :max="videoWidth" readonly :disabled="isUploading" />
                </div>
                <div class="option-item">
                  <label>裁剪Y (px)</label>
                  <input v-model.number="cropOptions.y" type="number" min="0" :max="videoHeight" readonly :disabled="isUploading" />
                </div>
              </div>
          <div class="option-row">
            <div class="option-item">
              <label>宽度 (max 720 px)</label>
                  <input v-model.number="cropOptions.width" type="number" min="100" :max="videoWidth" readonly :disabled="isUploading" />
            </div>
            <div class="option-item">
              <label>高度 (max 720 px)</label>
                  <input v-model.number="cropOptions.height" type="number" min="100" :max="videoHeight" readonly :disabled="isUploading" />
                </div>
              </div>
              
              <!-- 快捷裁剪比例 -->
              <div class="crop-presets">
                <label>快捷比例:</label>
                <div class="preset-buttons">
                  <button @click="applyCropPreset('16:9')" :class="['preset-btn', { active: getCurrentAspectRatio() === '16:9' }]" :disabled="isUploading">16:9</button>
                  <button @click="applyCropPreset('4:3')" :class="['preset-btn', { active: getCurrentAspectRatio() === '4:3' }]" :disabled="isUploading">4:3</button>
                  <button @click="applyCropPreset('3:2')" :class="['preset-btn', { active: getCurrentAspectRatio() === '3:2' }]" :disabled="isUploading">3:2</button>
                  <button @click="applyCropPreset('1:1')" :class="['preset-btn', { active: getCurrentAspectRatio() === '1:1' }]" :disabled="isUploading">1:1</button>
                  <button @click="applyCropPreset('9:16')" :class="['preset-btn', { active: getCurrentAspectRatio() === '9:16' }]" :disabled="isUploading">9:16</button>
                  <button @click="applyCropPreset('free')" :class="['preset-btn', 'preset-btn-free', { active: getCurrentAspectRatio() === 'free' }]" :disabled="isUploading">自由</button>
                </div>
            </div>
          </div>

          <div class="option-row">
            <div class="option-item">
                <label>宽度 (max 720 px)</label>
                <input v-model.number="conversionOptions.width" type="number" placeholder="留空保持原始比例" min="100" :disabled="isUploading" @input="handleWidthChange" />
              </div>
              <div class="option-item">
                <label>高度 (max 720 px)</label>
                <input v-model.number="conversionOptions.height" type="number" placeholder="留空保持原始比例" min="100" :disabled="isUploading" @input="handleHeightChange" />
              </div>
            </div>
          <div class="option-row">
            <div class="option-item" style="flex: 1;">
              <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                <input type="checkbox" v-model="conversionOptions.keepAspectRatio" :disabled="isUploading || !videoWidth || !videoHeight" />
                <span>保持宽高比</span>
              </label>
            </div>
          </div>
          <div class="option-row">
            <div class="option-item">
              <label>帧率 (1-30)</label>
                <input v-model.number="conversionOptions.fps" type="number" placeholder="默认10" min="1" max="30" @blur="validateFps" @input="validateFps" :disabled="isUploading" />
            </div>
            <div class="option-item">
              <label>质量 (1-100)</label>
                <input v-model.number="conversionOptions.quality" type="number" placeholder="默认80" min="1" max="100" @blur="validateQuality" @input="validateQuality" :disabled="isUploading" />
            </div>
          </div>
          <div class="option-row">
            <div class="option-item">
              <label>开始时间 (s)</label>
                <input v-model.number="conversionOptions.startTime" type="number" placeholder="开始时间" min="0" :max="videoDuration" step="0.01" @input="handleStartTimeInput" :disabled="isUploading" />
            </div>
            <div class="option-item">
              <label>持续时间 (max 12 s)</label>
                <input v-model.number="conversionOptions.duration" type="number" placeholder="持续时间" min="0.01" step="0.01" @input="handleDurationInput" :disabled="isUploading" />
            </div>
          </div>
            <button class="convert-btn" @click="handleConvert" :disabled="isUploading">
              <span v-if="!isUploading">开始转换</span>
              <span v-else class="converting-content">
                <span class="spinner-small"></span>
                转换中...
              </span>
          </button>
          </aside>
          
          <!-- 🔑 移动端：固定转换按钮（确保始终可见） -->
          <div v-if="videoUrl" class="mobile-convert-btn-wrapper">
            <button class="mobile-convert-btn" @click="handleConvert" :disabled="isUploading">
              <span v-if="!isUploading">开始转换</span>
              <span v-else class="converting-content">
                <span class="spinner-small"></span>
                转换中...
              </span>
            </button>
          </div>
        </div>
      </div>

      <section class="tips-card">
        <h3>为什么选择 FreeConvert 视频转 GIF？</h3>
        <ul>
          <li>时间轴裁剪 + 画面裁剪双保险，动图体积更小、重点更突出</li>
          <li>自定义帧率、分辨率与质量，兼顾清晰度与加载速度</li>
          <li>上传后本地浏览器即时预览，完成即自动清理缓存文件</li>
        </ul>
      </section>

      </div>
    
  </div>
</template>

<script setup>
import { ref, reactive, watch, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { uploadAndConvert } from '@/api/conversion'
import { toastError } from '@/utils/toast'

const router = useRouter()

// 页面可见性状态
const wasPlayingBeforeHidden = ref(false)

// 组件挂载时添加页面可见性监听
onMounted(() => {
  // 监听页面可见性变化
  document.addEventListener('visibilitychange', handleVisibilityChange)
  
  // 监听窗口焦点变化（备用方案）
  window.addEventListener('focus', handleWindowFocus)
  window.addEventListener('blur', handleWindowBlur)
})

// 组件卸载时清理资源
onUnmounted(() => {
  // 清除页面可见性监听
  document.removeEventListener('visibilitychange', handleVisibilityChange)
  window.removeEventListener('focus', handleWindowFocus)
  window.removeEventListener('blur', handleWindowBlur)
  
  // 清除事件监听器
  document.removeEventListener('mousemove', handleMouseMove)
  document.removeEventListener('mouseup', handleMouseUp)
  
  // 🔑 关键：清除防抖定时器
  if (presetDebounceTimer) {
    clearTimeout(presetDebounceTimer)
    presetDebounceTimer = null
  }
  
  // 🔑 关键：取消未完成的动画帧
  if (rafId) {
    cancelAnimationFrame(rafId)
    rafId = null
  }
  
  // 释放video URL
  if (videoUrl.value) {
    URL.revokeObjectURL(videoUrl.value)
  }
})

// 状态变量
const selectedFile = ref(null)
const videoUrl = ref('')
const isUploading = ref(false)
const isDragOver = ref(false)
const uploadProgress = ref(0)
const videoPlayerStyle = ref({})
const uploadingText = ref('上传中...')
const fileInput = ref(null)
const videoPlayer = ref(null)
const trimmerTrack = ref(null)

// 视频相关状态
const videoDuration = ref(0)
const currentTime = ref(0)
const isPlaying = ref(false)
const trimStart = ref(0)
const trimEnd = ref(0)
const videoPreviewStrip = ref('')
const videoThumbnails = ref([])
const videoFps = ref(0)
const isGeneratingThumbnails = ref(false)
const videoWidth = ref(0)
const videoHeight = ref(0)

// 裁剪相关状态
const isCropMode = ref(false)
const cropOptions = reactive({
  enabled: false,
  x: 0,
  y: 0,
  width: 0,
  height: 0,
  aspectRatio: null // null表示自由裁剪，否则为比例值如16/9
})

// 初始裁剪框大小（用于比例调整的基准）
const initialCropSize = reactive({
  width: 0,
  height: 0,
  x: 0,
  y: 0
})

// 裁剪拖拽状态
let isCropDragging = false
let cropDragType = '' // 'tl', 'tr', 'bl', 'br', 't', 'r', 'b', 'l', 'move'
let cropDragStartX = 0
let cropDragStartY = 0
let cropDragStartCropX = 0
let cropDragStartCropY = 0
let cropDragStartCropWidth = 0
let cropDragStartCropHeight = 0

// 🔑 关键：缓存拖拽时的显示区域信息，避免频繁DOM查询
let dragDisplayArea = null
let dragVideoRect = null

// 🔑 关键：使用临时变量存储拖拽过程中的裁剪值，避免频繁触发Vue响应式更新
let tempCropX = 0
let tempCropY = 0
let tempCropWidth = 0
let tempCropHeight = 0
let rafId = null // requestAnimationFrame ID

// 🔑 关键：防止频繁点击比例按钮导致卡顿
let isApplyingPreset = false
let presetDebounceTimer = null

// 拖拽状态
let isDragging = false
let dragType = '' // 'start' or 'end'

// 转换参数
const conversionOptions = reactive({
  width: null,
  height: null,
  fps: 10,
  quality: 80,
  startTime: null,
  duration: null,
  keepAspectRatio: true // 保持宽高比（默认勾选）
})

// 原始视频宽高比（用于保持宽高比计算）
const originalAspectRatio = computed(() => {
  if (videoWidth.value && videoHeight.value) {
    return videoWidth.value / videoHeight.value
  }
  return null
})

// 🔑 监听保持宽高比选项的变化
watch(() => conversionOptions.keepAspectRatio, (newVal) => {
  if (newVal && originalAspectRatio.value) {
    // 如果勾选了保持宽高比，根据当前值自动调整
    if (conversionOptions.width && !conversionOptions.height) {
      // 只有宽度，计算高度
      conversionOptions.height = Math.round(conversionOptions.width / originalAspectRatio.value)
    } else if (conversionOptions.height && !conversionOptions.width) {
      // 只有高度，计算宽度
      conversionOptions.width = Math.round(conversionOptions.height * originalAspectRatio.value)
    } else if (conversionOptions.width && conversionOptions.height) {
      // 两者都有，以宽度为准重新计算高度（保持宽高比）
      conversionOptions.height = Math.round(conversionOptions.width / originalAspectRatio.value)
    }
  }
})

// 计算估计帧数
const estimatedFrames = computed(() => {
  const duration = trimEnd.value - trimStart.value
  const fps = conversionOptions.fps || 10
  return Math.round(duration * fps)
})

// 🔑 核心函数：计算视频在播放器中的实际显示区域
const getVideoDisplayArea = () => {
  if (!videoPlayer.value) return null
  
  let videoRect, playerW, playerH
  try {
    // 🔑 关键：安全地获取视频播放器尺寸，避免干扰播放状态
    videoRect = videoPlayer.value.getBoundingClientRect()
    playerW = videoRect.width
    playerH = videoRect.height
  } catch (error) {
    console.warn('获取视频显示区域失败:', error)
    return null
  }
  
  const videoW = videoWidth.value
  const videoH = videoHeight.value
  
  if (!videoW || !videoH || !playerW || !playerH) return null
  
  const aspectV = videoW / videoH
  const aspectP = playerW / playerH
  
  let displayW, displayH, offsetX, offsetY
  
  // object-fit: cover 的计算逻辑
  if (aspectV > aspectP) {
    // 视频更宽：播放器高度填满，视频左右被裁剪
    displayH = playerH
    displayW = playerH * aspectV
    offsetX = (playerW - displayW) / 2  // 负值，表示左右溢出
    offsetY = 0
  } else {
    // 视频更高：播放器宽度填满，视频上下被裁剪  
    displayW = playerW
    displayH = playerW / aspectV
    offsetX = 0
    offsetY = (playerH - displayH) / 2  // 负值，表示上下溢出
  }
  
  // 🔑 关键：计算视频内容在播放器中的可见区域
  const visibleLeft = Math.max(0, -offsetX)
  const visibleTop = Math.max(0, -offsetY)
  const visibleRight = Math.min(displayW, playerW - offsetX)
  const visibleBottom = Math.min(displayH, playerH - offsetY)
  const visibleW = visibleRight - visibleLeft
  const visibleH = visibleBottom - visibleTop
  
  // 映射到视频原始像素坐标
  const scaleX = videoW / displayW
  const scaleY = videoH / displayH
  
  return {
    // 播放器坐标系（用于CSS定位）
    playerLeft: Math.max(0, offsetX),
    playerTop: Math.max(0, offsetY),
    playerWidth: visibleW,
    playerHeight: visibleH,
    
    // 视频像素坐标系（用于裁剪计算）
    videoLeft: visibleLeft * scaleX,
    videoTop: visibleTop * scaleY,
    videoWidth: visibleW * scaleX,
    videoHeight: visibleH * scaleY,
    
    // 缩放比例
    scaleX: visibleW / (visibleW * scaleX),
    scaleY: visibleH / (visibleH * scaleY),
    
    // 完整显示区域信息
    fullDisplayW: displayW,
    fullDisplayH: displayH,
    offsetX,
    offsetY
  }
}

// 计算裁剪区域样式（严格基于视频可见区域）
const cropAreaStyle = computed(() => {
  const displayArea = getVideoDisplayArea()
  if (!displayArea) return {}
  
  // 🔑 关键：拖拽时使用临时变量，避免频繁触发响应式更新
  const x = isCropDragging ? tempCropX : cropOptions.x
  const y = isCropDragging ? tempCropY : cropOptions.y
  const w = isCropDragging ? tempCropWidth : cropOptions.width
  const h = isCropDragging ? tempCropHeight : cropOptions.height
  
  // 🔑 关键：裁剪坐标必须相对于视频可见区域
  const relativeX = x - displayArea.videoLeft
  const relativeY = y - displayArea.videoTop
  const relativeW = w
  const relativeH = h
  
  // 映射到播放器坐标
  const scaleX = displayArea.playerWidth / displayArea.videoWidth
  const scaleY = displayArea.playerHeight / displayArea.videoHeight
  
  return {
    left: (displayArea.playerLeft + relativeX * scaleX) + 'px',
    top: (displayArea.playerTop + relativeY * scaleY) + 'px',
    width: (relativeW * scaleX) + 'px',
    height: (relativeH * scaleY) + 'px'
  }
})

// 标记是否正在更新（防止循环触发）
let isUpdating = false

// 监听裁剪变化，自动更新转换参数
watch([trimStart, trimEnd], () => {
  if (isUpdating) return
  isUpdating = true
  conversionOptions.startTime = parseFloat(trimStart.value.toFixed(2))
  conversionOptions.duration = parseFloat((trimEnd.value - trimStart.value).toFixed(2))
  isUpdating = false
})

// 监听裁剪区间变化，自动从起始位置重新播放
watch([trimStart, trimEnd], () => {
  if (!videoPlayer.value || isUpdating) return
  
  // 跳转到新的起始位置
  videoPlayer.value.currentTime = trimStart.value
  
  // 自动播放
  if (videoPlayer.value.paused) {
    videoPlayer.value.play().catch(err => {
      console.error('自动播放失败:', err)
    })
  }
})

// 移除了预览模式相关的watch

// 处理开始时间输入
const handleStartTimeInput = () => {
  if (isUpdating) return
  
  let startTime = conversionOptions.startTime
  if (startTime === null || startTime === undefined || isNaN(startTime)) {
    return
  }
  
  // 限制范围
  startTime = Math.max(0, Math.min(startTime, videoDuration.value))
  
  isUpdating = true
  trimStart.value = startTime
  
  // 确保结束时间有效
  if (trimEnd.value <= trimStart.value) {
    trimEnd.value = Math.min(trimStart.value + 0.1, videoDuration.value)
    conversionOptions.duration = parseFloat((trimEnd.value - trimStart.value).toFixed(2))
  } else {
    conversionOptions.duration = parseFloat((trimEnd.value - trimStart.value).toFixed(2))
  }
  
  conversionOptions.startTime = parseFloat(trimStart.value.toFixed(2))
  isUpdating = false
}

// 验证并修正帧率输入（1-30，整数）
const validateFps = () => {
  let fps = conversionOptions.fps
  
  // 如果为空或无效，设置默认值
  if (fps === null || fps === undefined || isNaN(fps)) {
    conversionOptions.fps = 10
    return
  }
  
  // 取整数
  fps = Math.floor(fps)
  
  // 限制范围 1-30
  fps = Math.max(1, Math.min(30, fps))
  
  conversionOptions.fps = fps
}

// 验证并修正质量输入（1-100，整数）
const validateQuality = () => {
  let quality = conversionOptions.quality
  
  // 如果为空或无效，设置默认值
  if (quality === null || quality === undefined || isNaN(quality)) {
    conversionOptions.quality = 80
    return
  }
  
  // 取整数
  quality = Math.floor(quality)
  
  // 限制范围 1-100
  quality = Math.max(1, Math.min(100, quality))
  
  conversionOptions.quality = quality
}

// 处理持续时间输入
const handleDurationInput = () => {
  if (isUpdating) return
  
  let duration = conversionOptions.duration
  if (duration === null || duration === undefined || isNaN(duration)) {
    return
  }
  
  // 限制最小值
  duration = Math.max(0.01, duration)
  
  isUpdating = true
  
  // 计算新的结束时间
  let newEnd = trimStart.value + duration
  
  // 如果超出视频长度，调整开始时间
  if (newEnd > videoDuration.value) {
    newEnd = videoDuration.value
    trimStart.value = Math.max(0, newEnd - duration)
  }
  
  trimEnd.value = newEnd
  
  conversionOptions.startTime = parseFloat(trimStart.value.toFixed(2))
  conversionOptions.duration = parseFloat((trimEnd.value - trimStart.value).toFixed(2))
  isUpdating = false
}

// 处理文件选择
const handleFileSelect = (event) => {
  const file = event.target.files[0]
  if (file) {
    validateAndSetFile(file)
  }
}

// 处理文件拖拽
const handleDrop = (event) => {
  isDragOver.value = false
  const file = event.dataTransfer.files[0]
  if (file) {
    validateAndSetFile(file)
  }
}

// 验证并设置文件
const validateAndSetFile = (file) => {
  // 检查文件类型
  if (!file.type.startsWith('video/')) {
    toastError('请选择视频文件')
    return
  }
  
  // 检查文件大小（100MB）
  if (file.size > 100 * 1024 * 1024) {
    toastError('文件大小不能超过100MB')
    return
  }
  
  selectedFile.value = file

  // 生成本地预览地址
  if (videoUrl.value) {
    URL.revokeObjectURL(videoUrl.value)
  }
  videoUrl.value = URL.createObjectURL(file)
}

// 视频加载完成
const handleVideoLoaded = async () => {
  if (videoPlayer.value) {
    videoDuration.value = videoPlayer.value.duration
    trimStart.value = 0
    trimEnd.value = videoDuration.value
    currentTime.value = 0
    lastVideoTime = 0 // 重置卡顿检测
    stuckCheckCount = 0 // 重置卡顿计数
    
    // 获取视频原始尺寸
    videoWidth.value = videoPlayer.value.videoWidth
    videoHeight.value = videoPlayer.value.videoHeight
    
    // 根据视频尺寸动态调整播放框大小
    const aspectRatio = videoWidth.value / videoHeight.value
    
    // 计算合适的高度
    let height
    if (aspectRatio > 1.5) {
      // 横屏视频 (16:9等)
      height = '350px'
    } else if (aspectRatio < 0.7) {
      // 竖屏视频 (9:16等)
      height = '500px'
    } else {
      // 接近方形的视频
      height = '420px'
    }
    
    videoPlayerStyle.value = {
      height: height,
      aspectRatio: `${videoWidth.value}/${videoHeight.value}`
    }
    
    // 自动填充宽度和高度为原视频尺寸
    conversionOptions.width = videoWidth.value
    conversionOptions.height = videoHeight.value
    // 如果保持宽高比已启用，确保初始值正确
    if (conversionOptions.keepAspectRatio && originalAspectRatio.value) {
      // 如果只设置了宽度或高度，自动计算另一个
      if (conversionOptions.width && !conversionOptions.height) {
        conversionOptions.height = Math.round(conversionOptions.width / originalAspectRatio.value)
      } else if (conversionOptions.height && !conversionOptions.width) {
        conversionOptions.width = Math.round(conversionOptions.height * originalAspectRatio.value)
      }
    }
    
    // 设置初始播放位置为裁剪起始位置
    videoPlayer.value.currentTime = 0
    
    // 🔑 关键：设置视频播放优化属性
    if (videoPlayer.value) {
      // 启用硬件加速（如果支持）
      videoPlayer.value.style.transform = 'translateZ(0)'
      videoPlayer.value.style.webkitTransform = 'translateZ(0)'
      
      // 设置视频播放质量
      if ('webkitDecodedFrameCount' in videoPlayer.value) {
        // Chrome/Edge 支持
        videoPlayer.value.setAttribute('playsinline', 'true')
      }
    }
    
    // 获取视频帧率（近似值）
    // 大多数视频是24, 25, 30, 60fps
    videoFps.value = 30 // 默认值，实际帧率需要从视频元数据获取
    
    // 生成视频缩略图帧
    await generateVideoThumbnails()
    
    // 确保视频开始播放
    // 使用 nextTick 和 setTimeout 确保 DOM 完全更新后再播放
    await nextTick()
    setTimeout(() => {
      if (videoPlayer.value && videoPlayer.value.paused) {
        videoPlayer.value.play().catch(err => {
          console.log('视频自动播放被阻止:', err)
          // 如果自动播放被阻止，设置 isPlaying 为 false
          isPlaying.value = false
        })
      }
    }, 100)
  }
}

// 检查帧是否为黑帧
const isBlackFrame = (canvas, ctx) => {
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
  const data = imageData.data
  let totalBrightness = 0
  let pixelCount = 0
  
  // 采样检查，每隔4个像素检查一次以提高性能
  for (let i = 0; i < data.length; i += 16) { // RGBA，所以每4个值是一个像素，每隔4个像素采样
    const r = data[i]
    const g = data[i + 1]
    const b = data[i + 2]
    const brightness = (r + g + b) / 3
    totalBrightness += brightness
    pixelCount++
  }
  
  const averageBrightness = totalBrightness / pixelCount
  return averageBrightness < 30 // 如果平均亮度小于30，认为是黑帧
}

// 生成视频缩略图（优化：暂停播放并减少seek操作）
const generateVideoThumbnails = async () => {
  if (!videoPlayer.value) return
  
  isGeneratingThumbnails.value = true
  isManipulatingVideo = true // 标记正在操作视频
  
  // 暂停视频并保存状态
  const wasPlaying = !videoPlayer.value.paused
  const originalTime = videoPlayer.value.currentTime
  if (wasPlaying) {
    videoPlayer.value.pause()
  }
  
  try {
    const video = videoPlayer.value
    const duration = video.duration
    
    // 根据视频时长动态计算缩略图数量
    // 短视频(<=30s): 6个缩略图（减少数量以加快加载）
    // 中等视频(30s-120s): 8个缩略图
    // 长视频(>120s): 10个缩略图
    let thumbnailCount
    if (duration <= 30) {
      thumbnailCount = 6
    } else if (duration <= 120) {
      thumbnailCount = 8
    } else {
      thumbnailCount = 10
    }
    
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    
    // 设置canvas尺寸
    canvas.width = 160
    canvas.height = 90
    
    const thumbnails = []
    
    for (let i = 0; i < thumbnailCount; i++) {
      let thumbnailGenerated = false
      let attempts = 0
      const maxAttempts = 5
      
      while (!thumbnailGenerated && attempts < maxAttempts) {
        try {
          // 计算时间点，添加一些随机偏移避免固定的黑帧位置
          let baseTime = (duration / thumbnailCount) * (i + 0.5)
          let timeOffset = attempts * 0.1 // 每次重试偏移0.1秒
          let time = Math.min(baseTime + timeOffset, duration - 0.1)
          
          // 确保时间不小于0.1秒，避免视频开头的黑帧
          time = Math.max(time, 0.1)
          
          // 设置视频时间
          video.currentTime = time
          
          // 等待视频跳转到指定时间
          await new Promise((resolve) => {
            const onSeeked = () => {
              try {
                // 等待一小段时间确保帧完全加载
                setTimeout(() => {
                  // 清空画布
                  ctx.clearRect(0, 0, canvas.width, canvas.height)
                  
                  // 绘制当前帧到canvas
                  ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
                  
                  // 检查是否为黑帧
                  if (!isBlackFrame(canvas, ctx)) {
                    // 转换为data URL
                    const dataUrl = canvas.toDataURL('image/jpeg', 0.8)
                    thumbnails.push({
                      time: time,
                      position: (i / thumbnailCount) * 100,
                      width: (100 / thumbnailCount),
                      url: dataUrl
                    })
                    thumbnailGenerated = true
                  }
                  
                  video.removeEventListener('seeked', onSeeked)
                  resolve()
                }, 50) // 等待50ms确保帧加载
              } catch (error) {
                console.error('处理缩略图帧失败:', error)
                video.removeEventListener('seeked', onSeeked)
                resolve()
              }
            }
            
            video.addEventListener('seeked', onSeeked)
            
            // 超时处理
            setTimeout(() => {
              video.removeEventListener('seeked', onSeeked)
              resolve()
            }, 2000)
          })
          
          attempts++
        } catch (error) {
          console.error('生成缩略图失败:', error)
          attempts++
        }
      }
      
      // 如果多次尝试后仍然没有生成缩略图，使用一个默认的占位符
      if (!thumbnailGenerated) {
        // 创建一个渐变占位符
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
        gradient.addColorStop(0, '#667eea')
        gradient.addColorStop(1, '#764ba2')
        ctx.fillStyle = gradient
        ctx.fillRect(0, 0, canvas.width, canvas.height)
        
        // 添加时间文本
        ctx.fillStyle = 'white'
        ctx.font = '12px Arial'
        ctx.textAlign = 'center'
        ctx.fillText(`${((duration / thumbnailCount) * (i + 0.5)).toFixed(1)}s`, canvas.width / 2, canvas.height / 2)
        
        const dataUrl = canvas.toDataURL('image/jpeg', 0.8)
        thumbnails.push({
          time: (duration / thumbnailCount) * (i + 0.5),
          position: (i / thumbnailCount) * 100,
          width: (100 / thumbnailCount),
          url: dataUrl
        })
      }
    }
    
    videoThumbnails.value = thumbnails
    
    // 恢复视频状态
    await nextTick()
    video.currentTime = originalTime
    
    // 恢复播放状态
    if (wasPlaying) {
      setTimeout(() => {
        if (video && wasPlaying) {
          video.play().catch(err => {
            console.log('恢复播放失败:', err)
          })
        }
      }, 100)
    }
  } finally {
    isGeneratingThumbnails.value = false
    // 延迟重置操作标记
    setTimeout(() => {
      isManipulatingVideo = false
    }, 200)
  }
}

// 视频时间更新（优化：降低更新频率并避免在操作期间干扰）
let lastUpdateTime = 0
let isManipulatingVideo = false // 标记是否正在操作视频（拖拽、调整等）
let lastVideoTime = 0 // 记录上次视频时间，用于检测卡顿
let stuckCheckCount = 0 // 卡顿检测计数

const handleVideoTimeUpdate = () => {
  if (!videoPlayer.value) return
  
  // 如果正在操作视频（拖拽裁剪框、调整参数等），跳过本次更新
  if (isManipulatingVideo || isCropDragging || isDragging) {
    return
  }
  
  const now = performance.now()
  // 节流：每300ms更新一次UI，进一步减少重绘频率
  if (now - lastUpdateTime < 300) return
  
  const newTime = videoPlayer.value.currentTime
  currentTime.value = newTime
  lastUpdateTime = now
  
  // 🔑 关键：检测视频是否卡住（时间没有更新）
  if (isPlaying.value && !videoPlayer.value.paused) {
    if (Math.abs(newTime - lastVideoTime) < 0.01) {
      // 时间几乎没有变化，可能卡住了
      stuckCheckCount++
      if (stuckCheckCount > 3) {
        // 连续3次检测到卡顿，尝试恢复
        console.warn('检测到视频卡顿，尝试恢复播放')
        const savedTime = videoPlayer.value.currentTime
        
        // 尝试重新加载视频
        if (videoPlayer.value.readyState < 2) {
          videoPlayer.value.load()
          videoPlayer.value.addEventListener('loadeddata', function onLoadedData() {
            videoPlayer.value.removeEventListener('loadeddata', onLoadedData)
            videoPlayer.value.currentTime = savedTime
            videoPlayer.value.play().catch(err => {
              console.error('恢复播放失败:', err)
            })
          }, { once: true })
        } else {
          // 视频数据充足，尝试跳转一小段时间来"唤醒"播放器
          videoPlayer.value.currentTime = savedTime + 0.01
          videoPlayer.value.play().catch(err => {
            console.error('恢复播放失败:', err)
          })
        }
        
        stuckCheckCount = 0
      }
    } else {
      // 时间正常更新，重置卡顿计数
      stuckCheckCount = 0
    }
  } else {
    // 视频暂停或不在播放状态，重置卡顿计数
    stuckCheckCount = 0
  }
  
  lastVideoTime = newTime
  
  // 如果播放到裁剪结束位置，循环回到裁剪起始位置（不暂停）
  if (newTime >= trimEnd.value) {
    // 使用微任务确保不阻塞当前帧
    Promise.resolve().then(() => {
      if (videoPlayer.value && !isManipulatingVideo) {
        videoPlayer.value.currentTime = trimStart.value
        // 确保在循环时继续播放
        if (videoPlayer.value.paused && isPlaying.value) {
          videoPlayer.value.play().catch(err => {
            console.log('循环播放失败:', err)
          })
        }
      }
    })
  }
  
  // 如果播放位置在裁剪区间之前，跳转到起始位置
  if (newTime < trimStart.value) {
    Promise.resolve().then(() => {
      if (videoPlayer.value && !isManipulatingVideo) {
        videoPlayer.value.currentTime = trimStart.value
        // 确保继续播放
        if (videoPlayer.value.paused && isPlaying.value) {
          videoPlayer.value.play().catch(err => {
            console.log('恢复播放失败:', err)
          })
        }
      }
    })
  }
}

// 视频播放事件
const handleVideoPlay = () => {
  if (!videoPlayer.value) return
  
  // 如果当前时间不在裁剪范围内，跳转到裁剪起始位置
  if (videoPlayer.value.currentTime < trimStart.value || videoPlayer.value.currentTime >= trimEnd.value) {
    videoPlayer.value.currentTime = trimStart.value
  }
  
  isPlaying.value = true
}

// 视频暂停事件
const handleVideoPause = () => {
  isPlaying.value = false
}

// 视频播放结束事件（整个视频结束时也循环回裁剪起始位置）
const handleVideoEnded = () => {
  if (videoPlayer.value) {
    videoPlayer.value.currentTime = trimStart.value
    // 继续播放，不暂停
    videoPlayer.value.play()
  }
}

// 🔑 关键：视频等待数据加载（缓冲）
const handleVideoWaiting = () => {
  if (!videoPlayer.value) return
  // 视频正在缓冲，可以显示加载提示或暂停播放
  console.log('视频正在缓冲...')
}

// 🔑 关键：视频加载停滞（网络问题）
const handleVideoStalled = () => {
  if (!videoPlayer.value) return
  console.warn('视频加载停滞，尝试恢复...')
  
  // 如果视频正在播放，尝试重新加载
  if (isPlaying.value && !videoPlayer.value.paused) {
    const savedTime = videoPlayer.value.currentTime
    videoPlayer.value.load()
    
    videoPlayer.value.addEventListener('loadeddata', function onLoadedData() {
      videoPlayer.value.removeEventListener('loadeddata', onLoadedData)
      videoPlayer.value.currentTime = savedTime
      
      setTimeout(() => {
        if (videoPlayer.value && isPlaying.value) {
          videoPlayer.value.play().catch(err => {
            console.error('恢复播放失败:', err)
          })
        }
      }, 100)
    }, { once: true })
  }
}

// 🔑 关键：视频可以播放
const handleVideoCanPlay = () => {
  if (!videoPlayer.value) return
  // 视频可以播放，确保播放状态正确
  if (isPlaying.value && videoPlayer.value.paused) {
    videoPlayer.value.play().catch(err => {
      console.log('自动播放被阻止:', err)
    })
  }
}

// 🔑 关键：视频可以流畅播放（已加载足够数据）
const handleVideoCanPlayThrough = () => {
  if (!videoPlayer.value) return
  // 视频已加载足够数据，可以流畅播放
  if (isPlaying.value && videoPlayer.value.paused) {
    videoPlayer.value.play().catch(err => {
      console.log('自动播放被阻止:', err)
    })
  }
}

// 页面可见性变化处理（优化：更可靠的播放恢复机制）
const handleVisibilityChange = () => {
  if (!videoPlayer.value) return
  
  if (document.hidden) {
    // 页面隐藏时，记录当前播放状态和播放位置
    wasPlayingBeforeHidden.value = !videoPlayer.value.paused
    // 主动暂停视频以节省资源
    if (!videoPlayer.value.paused) {
      videoPlayer.value.pause()
    }
  } else {
    // 页面重新可见时，如果之前在播放，则恢复播放
    if (wasPlayingBeforeHidden.value && isPlaying.value) {
      // 使用多重延迟确保页面完全激活
      setTimeout(() => {
        if (videoPlayer.value && wasPlayingBeforeHidden.value) {
          // 🔑 关键：先重新加载视频，确保解码器正常
          const savedTime = videoPlayer.value.currentTime
          const savedSrc = videoPlayer.value.src
          
          // 如果视频卡住，尝试重新加载
          if (videoPlayer.value.readyState < 2) {
            // 视频未加载足够数据，重新加载
            videoPlayer.value.load()
          }
          
          // 等待视频重新加载
          videoPlayer.value.addEventListener('loadeddata', function onLoadedData() {
            videoPlayer.value.removeEventListener('loadeddata', onLoadedData)
            
            // 恢复播放位置
            videoPlayer.value.currentTime = savedTime
            
            // 再尝试播放
            setTimeout(() => {
              if (videoPlayer.value) {
                videoPlayer.value.play().catch(err => {
                  console.log('自动播放被阻止，尝试用户交互后恢复:', err)
                  // 如果自动播放失败，保持isPlaying状态，下次用户交互时恢复
                  isPlaying.value = true
                })
              }
            }, 100)
          }, { once: true })
          
          // 如果视频已经加载足够数据，直接恢复播放
          if (videoPlayer.value.readyState >= 2) {
            videoPlayer.value.currentTime = savedTime
            setTimeout(() => {
              if (videoPlayer.value) {
                videoPlayer.value.play().catch(err => {
                  console.log('自动播放被阻止:', err)
                  isPlaying.value = true
                })
              }
            }, 50)
          }
        }
      }, 200)
    }
  }
}

// 窗口获得焦点（优化：更智能的播放恢复）
const handleWindowFocus = () => {
  if (!videoPlayer.value) return
  
  // 窗口获得焦点时，如果视频应该在播放但被暂停了，尝试恢复播放
  if (isPlaying.value && videoPlayer.value.paused && !isManipulatingVideo) {
    // 延迟恢复，避免与其他操作冲突
    setTimeout(() => {
      if (videoPlayer.value && isPlaying.value && videoPlayer.value.paused) {
        // 🔑 关键：检查视频是否卡住（readyState < 2 表示数据不足）
        if (videoPlayer.value.readyState < 2) {
          // 视频数据不足，重新加载
          const savedTime = videoPlayer.value.currentTime
          videoPlayer.value.load()
          
          videoPlayer.value.addEventListener('loadeddata', function onLoadedData() {
            videoPlayer.value.removeEventListener('loadeddata', onLoadedData)
            videoPlayer.value.currentTime = savedTime
            
            setTimeout(() => {
              if (videoPlayer.value) {
                videoPlayer.value.play().catch(err => {
                  console.log('恢复播放失败:', err)
                  isPlaying.value = false
                })
              }
            }, 100)
          }, { once: true })
        } else {
          // 视频数据充足，直接恢复播放
          // 先确保视频在正确的位置
          if (videoPlayer.value.currentTime < trimStart.value || videoPlayer.value.currentTime >= trimEnd.value) {
            videoPlayer.value.currentTime = trimStart.value
          }
          
          videoPlayer.value.play().catch(err => {
            console.log('恢复播放失败:', err)
            // 如果恢复播放失败，更新状态
            isPlaying.value = false
          })
        }
      }
    }, 150)
  }
}

// 窗口失去焦点
const handleWindowBlur = () => {
  if (!videoPlayer.value) return
  
  // 记录当前播放状态
  wasPlayingBeforeHidden.value = !videoPlayer.value.paused
}

// 点击视频切换播放/暂停
// 🔑 关键：防止拖拽时意外触发视频点击
let isVideoMouseDown = false
let videoMouseDownTime = 0
let isVideoTouchStart = false
let videoTouchStartTime = 0

const handleVideoMouseDown = () => {
  isVideoMouseDown = true
  videoMouseDownTime = Date.now()
  
  // 🔑 关键：添加全局mouseup监听器来重置标志
  const resetMouseDown = () => {
    isVideoMouseDown = false
    document.removeEventListener('mouseup', resetMouseDown)
  }
  document.addEventListener('mouseup', resetMouseDown)
}

// 🔑 移动端：触摸开始
const handleVideoTouchStart = (event) => {
  isVideoTouchStart = true
  videoTouchStartTime = Date.now()
  
  // 🔑 关键：添加全局touchend监听器来重置标志
  const resetTouchStart = () => {
    isVideoTouchStart = false
    document.removeEventListener('touchend', resetTouchStart)
    document.removeEventListener('touchcancel', resetTouchStart)
  }
  document.addEventListener('touchend', resetTouchStart, { once: true })
  document.addEventListener('touchcancel', resetTouchStart, { once: true })
}

// 🔑 移动端：触摸结束
const handleVideoTouchEnd = (event) => {
  // 🔑 关键：如果正在拖拽裁剪框，或者触摸时间过长（可能是拖拽），则忽略点击
  if (isCropDragging || (Date.now() - videoTouchStartTime > 200)) {
    event.preventDefault()
    event.stopPropagation()
    isVideoTouchStart = false
    return
  }
  
  // 简单的触摸（点击），切换播放/暂停
  if (isVideoTouchStart) {
    event.preventDefault()
    toggleVideoPlayPause()
    isVideoTouchStart = false
  }
}

const handleVideoClick = (event) => {
  // 🔑 关键：如果正在拖拽裁剪框，或者鼠标按下时间过长（可能是拖拽），则忽略点击
  if (isCropDragging || (Date.now() - videoMouseDownTime > 200)) {
    event.preventDefault()
    event.stopPropagation()
    isVideoMouseDown = false
    return
  }
  
  isVideoMouseDown = false
  toggleVideoPlayPause()
}

const toggleVideoPlayPause = () => {
  if (!videoPlayer.value) return
  
  if (videoPlayer.value.paused) {
    videoPlayer.value.play()
  } else {
    videoPlayer.value.pause()
  }
}

// 格式化时间
const formatTime = (seconds) => {
  if (!seconds || isNaN(seconds)) return '0.00s'
  return seconds.toFixed(2) + 's'
}

// 播放裁剪片段
const playTrimmedSection = () => {
  if (!videoPlayer.value) return
  
  if (isPlaying.value) {
    videoPlayer.value.pause()
    // isPlaying会在handleVideoPause中设置为false
  } else {
    // 智能判断播放起始位置
    const currentPos = videoPlayer.value.currentTime
    
    // 如果当前进度在裁剪范围内，从当前位置继续播放
    // 如果在范围外，从裁剪开始位置播放
    if (currentPos >= trimStart.value && currentPos < trimEnd.value) {
      // 当前位置在裁剪范围内，从当前位置继续
      videoPlayer.value.play()
    } else {
      // 当前位置在裁剪范围外，跳转到裁剪开始位置
      videoPlayer.value.currentTime = trimStart.value
      videoPlayer.value.play()
    }
    // isPlaying会在handleVideoPlay中设置为true
  }
}

// 重置裁剪
const resetTrim = () => {
  trimStart.value = 0
  trimEnd.value = videoDuration.value
  if (videoPlayer.value) {
    videoPlayer.value.currentTime = 0
    videoPlayer.value.pause()
    isPlaying.value = false
  }
}

// 手柄拖拽开始
const handleHandleMouseDown = (type, event) => {
  // 如果正在转换中，禁止拖拽
  if (isUploading.value) {
    return
  }
  
  isDragging = true
  isManipulatingVideo = true // 标记正在操作视频
  dragType = type
  event.preventDefault()
  
  const rect = trimmerTrack.value.getBoundingClientRect()
  selectionDragStartX = getEventX(event) - rect.left
  
  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}

// 🔑 移动端：触摸手柄
const handleHandleTouchStart = (type, event) => {
  event.preventDefault()
  handleHandleMouseDown(type, event)
  
  // 添加触摸移动和结束监听
  document.addEventListener('touchmove', handleTouchMove, { passive: false })
  document.addEventListener('touchend', handleMouseUp)
  document.addEventListener('touchcancel', handleMouseUp)
}

// 两端加厚区域（开始/结束）触发拖拽
const handleEdgeGutterMouseDown = (type, event) => {
  handleHandleMouseDown(type, event)
}
const handleEdgeGutterTouchStart = (type, event) => {
  event.preventDefault()
  handleHandleTouchStart(type, event)
}

// 选区平移开始
let selectionDragStartX = 0
let selectionDragStartTime = 0
let selectionDragEndTime = 0

const handleSelectionMouseDown = (event) => {
  // 如果正在转换中，禁止拖拽
  if (isUploading.value) {
    return
  }
  
  // 如果点击的是手柄，不处理平移
  if (event.target.classList.contains('trimmer-handle') || 
      event.target.classList.contains('trimmer-handle-icon')) {
    return
  }
  
  isDragging = true
  isManipulatingVideo = true // 标记正在操作视频
  dragType = 'selection'
  event.preventDefault()
  
  const rect = trimmerTrack.value.getBoundingClientRect()
  selectionDragStartX = getEventX(event) - rect.left
  selectionDragStartTime = trimStart.value
  selectionDragEndTime = trimEnd.value
  
  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}

// 🔑 移动端：触摸选区
const handleSelectionTouchStart = (event) => {
  event.preventDefault()
  handleSelectionMouseDown(event)
  
  // 添加触摸移动和结束监听
  document.addEventListener('touchmove', handleTouchMove, { passive: false })
  document.addEventListener('touchend', handleMouseUp)
  document.addEventListener('touchcancel', handleMouseUp)
}

// 鼠标移动（优化：降低处理频率到30fps）
let lastMouseMoveTime = 0
const handleMouseMove = (event) => {
  if (!isDragging || !trimmerTrack.value) return
  
  const now = performance.now()
  // 节流：每33ms（约30fps）处理一次，平衡流畅度和性能
  if (now - lastMouseMoveTime < 33) return
  lastMouseMoveTime = now
  
  const rect = trimmerTrack.value.getBoundingClientRect()
  const x = Math.max(0, Math.min(getEventX(event) - rect.left, rect.width))
  const percentage = x / rect.width
  const time = percentage * videoDuration.value
  
  if (dragType === 'start') {
    trimStart.value = Math.max(0, Math.min(time, trimEnd.value - 0.1))
  } else if (dragType === 'end') {
    trimEnd.value = Math.max(trimStart.value + 0.1, Math.min(time, videoDuration.value))
  } else if (dragType === 'selection') {
    // 选区平移
    const currentX = getEventX(event) - rect.left
    const deltaX = currentX - selectionDragStartX
    const deltaPercentage = deltaX / rect.width
    const deltaTime = deltaPercentage * videoDuration.value
    
    const duration = selectionDragEndTime - selectionDragStartTime
    let newStart = selectionDragStartTime + deltaTime
    let newEnd = selectionDragEndTime + deltaTime
    
    // 边界检查
    if (newStart < 0) {
      newStart = 0
      newEnd = duration
    } else if (newEnd > videoDuration.value) {
      newEnd = videoDuration.value
      newStart = videoDuration.value - duration
    }
    
    trimStart.value = newStart
    trimEnd.value = newEnd
  }
}

// 🔑 移动端：触摸移动
const handleTouchMove = (event) => {
  if (!isDragging || !trimmerTrack.value) return
  event.preventDefault() // 防止页面滚动
  handleMouseMove(event)
}

// 鼠标释放
const handleMouseUp = () => {
  isDragging = false
  dragType = ''
  
  // 延迟重置操作标记
  setTimeout(() => {
    isManipulatingVideo = false
  }, 100)
  
  document.removeEventListener('mousemove', handleMouseMove)
  document.removeEventListener('mouseup', handleMouseUp)
  document.removeEventListener('touchmove', handleTouchMove)
  document.removeEventListener('touchend', handleMouseUp)
  document.removeEventListener('touchcancel', handleMouseUp)
}

// 获取触摸或鼠标事件的坐标
const getEventX = (event) => {
  if (event.touches && event.touches.length > 0) {
    return event.touches[0].clientX
  }
  return event.clientX
}

// 点击轨道
const handleTrackClick = (event) => {
  // 如果正在转换中，禁止点击调整
  if (isUploading.value) {
    return
  }
  
  if (event.target !== trimmerTrack.value) return
  
  const rect = trimmerTrack.value.getBoundingClientRect()
  const x = getEventX(event) - rect.left
  const percentage = x / rect.width
  const time = percentage * videoDuration.value
  
  // 移动最近的手柄
  const distToStart = Math.abs(time - trimStart.value)
  const distToEnd = Math.abs(time - trimEnd.value)
  
  if (distToStart < distToEnd) {
    trimStart.value = Math.max(0, Math.min(time, trimEnd.value - 0.1))
  } else {
    trimEnd.value = Math.max(trimStart.value + 0.1, Math.min(time, videoDuration.value))
  }
}

// 🔑 移动端：触摸轨道
const handleTrackTouchStart = (event) => {
  event.preventDefault()
  handleTrackClick(event)
}

// 开始转换（优化：异步模式，立即返回任务ID并轮询状态）
const handleConvert = async () => {
  if (!selectedFile.value) {
    toastError('请先选择视频文件')
    return
  }
  
  // 防止重复点击
  if (isUploading.value) {
    return
  }
  
  isUploading.value = true
  uploadingText.value = '上传中...'
  
  try {
    // 构建FormData
    const formData = new FormData()
    formData.append('file', selectedFile.value)
    
    if (conversionOptions.width) {
      formData.append('width', conversionOptions.width)
    }
    if (conversionOptions.height) {
      formData.append('height', conversionOptions.height)
    }
    formData.append('fps', conversionOptions.fps)
    formData.append('quality', conversionOptions.quality)
    if (conversionOptions.startTime !== null && conversionOptions.startTime >= 0) {
      formData.append('startTime', conversionOptions.startTime)
    }
    if (conversionOptions.duration !== null && conversionOptions.duration > 0) {
      formData.append('duration', conversionOptions.duration)
    }
    
    // 如果启用了裁剪，添加裁剪参数
    if (cropOptions.enabled && cropOptions.width > 0 && cropOptions.height > 0) {
      formData.append('cropX', cropOptions.x)
      formData.append('cropY', cropOptions.y)
      formData.append('cropWidth', cropOptions.width)
      formData.append('cropHeight', cropOptions.height)
    }
    
    // 上传并转换（同步模式，性能更优）
    uploadingText.value = '转换中，请稍候...'
    const response = await uploadAndConvert(formData)
    
    // 检查响应格式（兼容旧的异步格式）
    if (response.data && response.data.taskId) {
      // 旧的异步模式：获取到任务ID，开始轮询状态（已废弃）
      const taskId = response.data.taskId
      uploadingText.value = '转换中，请稍候...'
      
      // 轮询任务状态
      const result = await pollConversionStatus(taskId)
      
      // 保存结果到sessionStorage
      sessionStorage.setItem('conversionResult', JSON.stringify(result))
      
      // 跳转到成功页面
      router.push('/success')
    } else {
      // 新的同步模式（性能更优）
      sessionStorage.setItem('conversionResult', JSON.stringify(response.data))
      router.push('/success')
    }
  } catch (error) {
    // 显示错误信息，优先显示后端返回的错误，否则显示通用错误
    const errorMsg = error.message || '网络错误，稍后重试'
    toastError(errorMsg)
  } finally {
    isUploading.value = false
  }
}

// 轮询转换状态
const pollConversionStatus = async (taskId, maxAttempts = 120, intervalMs = 1000) => {
  let attempts = 0
  
  while (attempts < maxAttempts) {
    try {
      // 调用查询接口
      const response = await fetch(`/api/conversion/videoToGif/${taskId}`)
      const result = await response.json()
      
      if (result.code === 200 && result.data) {
        const status = result.data.conversionStatus
        
        // 0: PENDING, 1: CONVERTING, 2: SUCCESS, 3: FAILED
        if (status === 2) {
          // 转换成功
          return result.data
        } else if (status === 3) {
          // 转换失败
          throw new Error(result.data.errorMessage || '转换失败')
        }
        
        // 状态为 PENDING 或 CONVERTING，继续轮询
        uploadingText.value = status === 0 ? '排队中...' : '转换中，请稍候...'
      }
      
      // 等待后再次查询
      await new Promise(resolve => setTimeout(resolve, intervalMs))
      attempts++
    } catch (error) {
      // 网络错误或其他异常
      if (attempts >= maxAttempts - 1) {
        throw error
      }
      // 否则继续重试
      await new Promise(resolve => setTimeout(resolve, intervalMs))
      attempts++
    }
  }
  
  // 超时
  throw new Error('转换超时，请稍后查看转换历史')
}

// 🔑 防止循环更新的标志
let isUpdatingAspectRatio = false

// 宽度变化时自动调整高度
const handleWidthChange = () => {
  if (isUpdatingAspectRatio || !conversionOptions.keepAspectRatio || !originalAspectRatio.value) {
    return
  }
  
  if (conversionOptions.width && originalAspectRatio.value) {
    isUpdatingAspectRatio = true
    conversionOptions.height = Math.round(conversionOptions.width / originalAspectRatio.value)
    // 确保高度是有效值
    if (conversionOptions.height < 100) {
      conversionOptions.height = 100
    }
    isUpdatingAspectRatio = false
  }
}

// 高度变化时自动调整宽度
const handleHeightChange = () => {
  if (isUpdatingAspectRatio || !conversionOptions.keepAspectRatio || !originalAspectRatio.value) {
    return
  }
  
  if (conversionOptions.height && originalAspectRatio.value) {
    isUpdatingAspectRatio = true
    conversionOptions.width = Math.round(conversionOptions.height * originalAspectRatio.value)
    // 确保宽度是有效值
    if (conversionOptions.width < 100) {
      conversionOptions.width = 100
    }
    isUpdatingAspectRatio = false
  }
}

// 重置
const handleReset = () => {
  selectedFile.value = null
  uploadProgress.value = 0
  if (videoUrl.value) {
    URL.revokeObjectURL(videoUrl.value)
  }
  videoUrl.value = ''
  
  // 重置视频状态
  videoDuration.value = 0
  currentTime.value = 0
  isPlaying.value = false
  trimStart.value = 0
  trimEnd.value = 0
  videoThumbnails.value = []
  videoFps.value = 0
  videoWidth.value = 0
  videoHeight.value = 0
  
  // 重置转换参数
  conversionOptions.width = null
  conversionOptions.height = null
  conversionOptions.fps = 10
  conversionOptions.quality = 80
  conversionOptions.startTime = null
  conversionOptions.duration = null
  conversionOptions.keepAspectRatio = true // 重置时也保持默认勾选
}

// 移除了预览相关函数

// 格式化文件大小
const formatFileSize = (bytes) => {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'
  if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
  return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
}

// ==================== 裁剪功能 ====================

// 启用裁剪模式
const enableCropMode = () => {
  isCropMode.value = true
  cropOptions.enabled = true
  
  // 🔑 关键：基于视频可见区域初始化裁剪框
  const displayArea = getVideoDisplayArea()
  if (displayArea) {
    const margin = 0.1
    cropOptions.x = Math.round(displayArea.videoLeft + displayArea.videoWidth * margin)
    cropOptions.y = Math.round(displayArea.videoTop + displayArea.videoHeight * margin)
    cropOptions.width = Math.round(displayArea.videoWidth * (1 - 2 * margin))
    cropOptions.height = Math.round(displayArea.videoHeight * (1 - 2 * margin))
  } else {
    // 备用方案
    const margin = 0.1
    cropOptions.x = Math.round(videoWidth.value * margin)
    cropOptions.y = Math.round(videoHeight.value * margin)
    cropOptions.width = Math.round(videoWidth.value * (1 - 2 * margin))
    cropOptions.height = Math.round(videoHeight.value * (1 - 2 * margin))
  }
  
  // 🔑 关键：保存初始裁剪框大小作为比例调整的基准
  initialCropSize.x = cropOptions.x
  initialCropSize.y = cropOptions.y
  initialCropSize.width = cropOptions.width
  initialCropSize.height = cropOptions.height
  
  // 🔑 关键：初始状态不选中任何比例，包括自由模式
  cropOptions.aspectRatio = undefined // undefined表示未选择任何模式
}

// 应用裁剪（确认裁剪）
const applyCrop = () => {
  isCropMode.value = false
  // cropOptions.enabled 保持为 true，这样参数会被包含在转换请求中
  
  // 更新输出尺寸为裁剪后的尺寸
  conversionOptions.width = cropOptions.width
  conversionOptions.height = cropOptions.height
}

// 取消裁剪
const cancelCrop = () => {
  isCropMode.value = false
  cropOptions.enabled = false
  cropOptions.x = 0
  cropOptions.y = 0
  cropOptions.width = 0
  cropOptions.height = 0
  cropOptions.aspectRatio = null
}

// 重置裁剪区域
const resetCrop = () => {
  // 🔑 关键：重置到初始裁剪框大小
  cropOptions.x = initialCropSize.x
  cropOptions.y = initialCropSize.y
  cropOptions.width = initialCropSize.width
  cropOptions.height = initialCropSize.height
  // 🔑 关键：重置到未选择任何比例的状态
  cropOptions.aspectRatio = undefined
}

// 获取当前宽高比对应的预设名称
const getCurrentAspectRatio = () => {
  // undefined 表示未选择任何模式
  if (cropOptions.aspectRatio === undefined) return 'none'
  // null 表示自由模式
  if (cropOptions.aspectRatio === null) return 'free'
  
  const ratio = cropOptions.aspectRatio
  if (Math.abs(ratio - 16/9) < 0.01) return '16:9'
  if (Math.abs(ratio - 4/3) < 0.01) return '4:3'
  if (Math.abs(ratio - 3/2) < 0.01) return '3:2'
  if (Math.abs(ratio - 1) < 0.01) return '1:1'
  if (Math.abs(ratio - 9/16) < 0.01) return '9:16'
  return 'free'
}

// 应用预设裁剪比例（优化：避免阻塞视频播放）
const applyCropPreset = async (preset) => {
  // 🔑 关键：如果正在执行，忽略新的点击
  if (isApplyingPreset) {
    return
  }
  
  // 🔑 关键：清除之前的防抖定时器
  if (presetDebounceTimer) {
    clearTimeout(presetDebounceTimer)
  }
  
  let aspectRatio = null
  
  switch(preset) {
    case '16:9':
      aspectRatio = 16 / 9
      break
    case '4:3':
      aspectRatio = 4 / 3
      break
    case '3:2':
      aspectRatio = 3 / 2
      break
    case '1:1':
      aspectRatio = 1
      break
    case '9:16':
      aspectRatio = 9 / 16
      break
    case 'free':
      aspectRatio = null
      break
  }
  
  // 🔑 关键：先立即设置比例（UI响应快）
  cropOptions.aspectRatio = aspectRatio
  
  // 🔑 关键：如果是自由模式，不需要复杂计算，直接返回
  if (aspectRatio === null) {
    return
  }
  
  // 🔑 关键：标记正在操作视频，避免timeupdate干扰
  isManipulatingVideo = true
  
  // 🔑 关键：使用防抖，延迟执行复杂计算
  presetDebounceTimer = setTimeout(async () => {
    await applyPresetCalculation(aspectRatio)
    presetDebounceTimer = null
    // 延迟重置标记
    setTimeout(() => {
      isManipulatingVideo = false
    }, 100)
  }, 150) // 150ms防抖延迟
}

// 执行预设比例的实际计算（从applyCropPreset中分离出来）
const applyPresetCalculation = async (aspectRatio) => {
  // 🔑 关键：标记正在执行，防止重复执行
  if (isApplyingPreset) return
  isApplyingPreset = true
  
  try {
    // 🔑 关键：使用 nextTick 确保DOM更新不干扰视频播放
    await nextTick()
    
    // 🔑 关键：暂时保存视频播放状态，避免被干扰
    const wasPlaying = videoPlayer.value && !videoPlayer.value.paused
    const currentTime = videoPlayer.value ? videoPlayer.value.currentTime : 0
    
    // 获取视频可见区域边界
    const displayArea = getVideoDisplayArea()
    if (!displayArea) return
    
    const visibleLeft = displayArea.videoLeft
    const visibleTop = displayArea.videoTop
    const visibleRight = displayArea.videoLeft + displayArea.videoWidth
    const visibleBottom = displayArea.videoTop + displayArea.videoHeight
    
    // 🔑 关键：基于初始裁剪框大小计算新的比例尺寸
    const initialAspect = initialCropSize.width / initialCropSize.height
    let newWidth, newHeight
    
    if (initialAspect > aspectRatio) {
      // 初始更宽，基于初始高度计算新宽度
      newHeight = initialCropSize.height
      newWidth = Math.round(newHeight * aspectRatio)
    } else {
      // 初始更高，基于初始宽度计算新高度
      newWidth = initialCropSize.width
      newHeight = Math.round(newWidth / aspectRatio)
    }
    
    // 🔑 关键：保持在初始位置的中心点
    const centerX = initialCropSize.x + initialCropSize.width / 2
    const centerY = initialCropSize.y + initialCropSize.height / 2
    
    let newX = Math.round(centerX - newWidth / 2)
    let newY = Math.round(centerY - newHeight / 2)
    
    // 🔑 关键：确保不超出视频可见区域边界
    if (newX < visibleLeft) {
      newX = visibleLeft
    }
    if (newY < visibleTop) {
      newY = visibleTop
    }
    if (newX + newWidth > visibleRight) {
      newX = visibleRight - newWidth
    }
    if (newY + newHeight > visibleBottom) {
      newY = visibleBottom - newHeight
    }
    
    // 🔑 关键：如果调整后的尺寸超出可见区域，需要等比缩放
    if (newWidth > displayArea.videoWidth || newHeight > displayArea.videoHeight) {
      const scaleW = displayArea.videoWidth / newWidth
      const scaleH = displayArea.videoHeight / newHeight
      const scale = Math.min(scaleW, scaleH)
      
      newWidth = Math.round(newWidth * scale)
      newHeight = Math.round(newHeight * scale)
      
      // 重新居中
      newX = Math.round(centerX - newWidth / 2)
      newY = Math.round(centerY - newHeight / 2)
      
      // 再次边界检查
      newX = Math.max(visibleLeft, Math.min(newX, visibleRight - newWidth))
      newY = Math.max(visibleTop, Math.min(newY, visibleBottom - newHeight))
    }
    
    // 应用新的裁剪参数
    cropOptions.x = newX
    cropOptions.y = newY
    cropOptions.width = newWidth
    cropOptions.height = newHeight
    
    // 🔑 关键：恢复视频播放状态，确保不被干扰
    await nextTick()
    if (videoPlayer.value && wasPlaying) {
      try {
        videoPlayer.value.currentTime = currentTime
        await videoPlayer.value.play()
      } catch (error) {
        console.warn('恢复视频播放失败:', error)
      }
    }
  } catch (error) {
    console.error('应用裁剪预设失败:', error)
  } finally {
    // 🔑 关键：无论成功或失败，都要重置执行标志
    isApplyingPreset = false
  }
}

// 裁剪区域拖拽开始（移动整个裁剪框）
const handleCropAreaMouseDown = (event) => {
  // 如果正在转换中，禁止拖拽
  if (isUploading.value) {
    return
  }
  
  event.preventDefault()
  isCropDragging = true
  isManipulatingVideo = true // 标记正在操作视频
  cropDragType = 'move'
  cropDragStartX = getEventX(event)
  cropDragStartY = event.touches ? event.touches[0].clientY : event.clientY
  cropDragStartCropX = cropOptions.x
  cropDragStartCropY = cropOptions.y
  
  // 🔑 关键：初始化临时变量
  tempCropX = cropOptions.x
  tempCropY = cropOptions.y
  tempCropWidth = cropOptions.width
  tempCropHeight = cropOptions.height
  
  // 🔑 关键：拖拽开始时缓存显示区域信息，避免拖拽过程中频繁DOM查询
  dragDisplayArea = getVideoDisplayArea()
  dragVideoRect = videoPlayer.value ? videoPlayer.value.getBoundingClientRect() : null
  
  document.addEventListener('mousemove', handleCropMouseMove)
  document.addEventListener('mouseup', handleCropMouseUp)
}

// 🔑 移动端：触摸裁剪区域
const handleCropAreaTouchStart = (event) => {
  event.preventDefault()
  handleCropAreaMouseDown(event)
  
  // 添加触摸移动和结束监听
  document.addEventListener('touchmove', handleCropTouchMove, { passive: false })
  document.addEventListener('touchend', handleCropMouseUp)
  document.addEventListener('touchcancel', handleCropMouseUp)
}

// 裁剪控制点拖拽开始
const handleCropHandleMouseDown = (type, event) => {
  // 如果正在转换中，禁止拖拽
  if (isUploading.value) {
    return
  }
  
  event.preventDefault()
  event.stopPropagation()
  isCropDragging = true
  isManipulatingVideo = true // 标记正在操作视频
  cropDragType = type
  cropDragStartX = getEventX(event)
  cropDragStartY = event.touches ? event.touches[0].clientY : event.clientY
  cropDragStartCropX = cropOptions.x
  cropDragStartCropY = cropOptions.y
  cropDragStartCropWidth = cropOptions.width
  cropDragStartCropHeight = cropOptions.height
  
  // 🔑 关键：初始化临时变量
  tempCropX = cropOptions.x
  tempCropY = cropOptions.y
  tempCropWidth = cropOptions.width
  tempCropHeight = cropOptions.height
  
  // 🔑 关键：拖拽开始时缓存显示区域信息，避免拖拽过程中频繁DOM查询
  dragDisplayArea = getVideoDisplayArea()
  dragVideoRect = videoPlayer.value ? videoPlayer.value.getBoundingClientRect() : null
  
  document.addEventListener('mousemove', handleCropMouseMove)
  document.addEventListener('mouseup', handleCropMouseUp)
}

// 🔑 移动端：触摸裁剪控制点
const handleCropHandleTouchStart = (type, event) => {
  event.preventDefault()
  event.stopPropagation()
  handleCropHandleMouseDown(type, event)
  
  // 添加触摸移动和结束监听
  document.addEventListener('touchmove', handleCropTouchMove, { passive: false })
  document.addEventListener('touchend', handleCropMouseUp)
  document.addEventListener('touchcancel', handleCropMouseUp)
}

// 裁剪拖拽移动
const handleCropMouseMove = (event) => {
  if (!isCropDragging || !videoPlayer.value) return
  
  // 🔑 关键：阻止事件传播，避免干扰视频播放器
  event.stopPropagation()
  
  // 🔑 关键：提取event属性，避免在异步回调中失效
  const clientX = getEventX(event)
  const clientY = event.touches ? event.touches[0].clientY : event.clientY
  
  // 🔑 关键：使用 requestAnimationFrame 节流，避免过度触发Vue响应式更新
  if (rafId) {
    return // 如果上一帧还没处理完，跳过本次事件
  }
  
  rafId = requestAnimationFrame(() => {
    updateCropPosition(clientX, clientY)
    rafId = null
  })
}

// 🔑 移动端：触摸移动裁剪
const handleCropTouchMove = (event) => {
  if (!isCropDragging || !videoPlayer.value) return
  event.preventDefault() // 防止页面滚动
  handleCropMouseMove(event)
}

// 更新裁剪位置的实际逻辑
const updateCropPosition = (clientX, clientY) => {
  if (!isCropDragging || !videoPlayer.value) return
  
  // 🔑 关键：使用缓存的显示区域信息，避免频繁DOM查询导致卡顿和播放器失去响应
  const displayArea = dragDisplayArea
  const videoRect = dragVideoRect
  if (!displayArea || !videoRect) return
  
  // 🔑 关键：鼠标坐标映射到视频可见区域
  const mouseX = clientX - videoRect.left - displayArea.playerLeft
  const mouseY = clientY - videoRect.top - displayArea.playerTop
  const startMouseX = cropDragStartX - videoRect.left - displayArea.playerLeft
  const startMouseY = cropDragStartY - videoRect.top - displayArea.playerTop
  
  // 映射到视频像素坐标
  const scaleX = displayArea.videoWidth / displayArea.playerWidth
  const scaleY = displayArea.videoHeight / displayArea.playerHeight
  
  const deltaX = (mouseX - startMouseX) * scaleX
  const deltaY = (mouseY - startMouseY) * scaleY
  let newX = cropDragStartCropX
  let newY = cropDragStartCropY
  let newWidth = cropDragStartCropWidth
  let newHeight = cropDragStartCropHeight
  switch(cropDragType) {
    case 'move':
      // 🔑 关键：移动时严格保持原有尺寸不变
      newWidth = cropOptions.width
      newHeight = cropOptions.height
      newX = cropDragStartCropX + deltaX
      newY = cropDragStartCropY + deltaY
      // 注意：边界检查在后面统一处理，这里不做任何修改
      break
    case 'tl':
      newX = cropDragStartCropX + deltaX
      newY = cropDragStartCropY + deltaY
      newWidth = cropDragStartCropWidth - deltaX
      newHeight = cropDragStartCropHeight - deltaY
      break
    case 'tr':
      newY = cropDragStartCropY + deltaY
      newWidth = cropDragStartCropWidth + deltaX
      newHeight = cropDragStartCropHeight - deltaY
      break
    case 'bl':
      newX = cropDragStartCropX + deltaX
      newWidth = cropDragStartCropWidth - deltaX
      newHeight = cropDragStartCropHeight + deltaY
      break
    case 'br':
      newWidth = cropDragStartCropWidth + deltaX
      newHeight = cropDragStartCropHeight + deltaY
      break
    case 't':
      newY = cropDragStartCropY + deltaY
      newHeight = cropDragStartCropHeight - deltaY
      break
    case 'r':
      newWidth = cropDragStartCropWidth + deltaX
      break
    case 'b':
      newHeight = cropDragStartCropHeight + deltaY
      break
    case 'l':
      newX = cropDragStartCropX + deltaX
      newWidth = cropDragStartCropWidth - deltaX
      break
  }
  // 最小尺寸约束
  if (cropDragType !== 'move') {
    const minSize = 100
    if (newWidth < minSize) {
      newWidth = minSize
      if (cropDragType.includes('l')) newX = cropDragStartCropX + cropDragStartCropWidth - minSize
    }
    if (newHeight < minSize) {
      newHeight = minSize
      if (cropDragType.includes('t')) newY = cropDragStartCropY + cropDragStartCropHeight - minSize
    }
  }
  // 宽高比锁定（只有在明确设置了比例时才锁定）
  if (cropOptions.aspectRatio !== null && cropOptions.aspectRatio !== undefined && cropDragType !== 'move') {
    const aspectRatio = cropOptions.aspectRatio
    if (cropDragType.includes('t') || cropDragType.includes('b')) {
      newWidth = Math.round(newHeight * aspectRatio)
      if (cropDragType.includes('l')) newX = cropDragStartCropX + cropDragStartCropWidth - newWidth
    } else {
      newHeight = Math.round(newWidth / aspectRatio)
      if (cropDragType.includes('t')) newY = cropDragStartCropY + cropDragStartCropHeight - newHeight
    }
  }
  // 🔑 关键：边界检查基于视频可见区域，而不是整个视频
  const visibleLeft = displayArea.videoLeft
  const visibleTop = displayArea.videoTop
  const visibleRight = displayArea.videoLeft + displayArea.videoWidth
  const visibleBottom = displayArea.videoTop + displayArea.videoHeight
  
  // 1. 先处理位置越界（小于可见区域左上角）
  if (newX < visibleLeft) {
    if (cropDragType === 'move') {
      newX = visibleLeft
    } else {
      newWidth += (newX - visibleLeft)  // 缩小宽度
      newX = visibleLeft
    }
  }
  if (newY < visibleTop) {
    if (cropDragType === 'move') {
      newY = visibleTop
    } else {
      newHeight += (newY - visibleTop)  // 缩小高度
      newY = visibleTop
    }
  }
  
  // 2. 再处理尺寸越界（超出可见区域右下角）
  if (newX + newWidth > visibleRight) {
    if (cropDragType === 'move') {
      newX = visibleRight - newWidth
    } else {
      newWidth = visibleRight - newX
    }
  }
  if (newY + newHeight > visibleBottom) {
    if (cropDragType === 'move') {
      newY = visibleBottom - newHeight
    } else {
      newHeight = visibleBottom - newY
    }
  }
  
  // 3. 确保最小尺寸（但不能破坏边界，移动操作除外）
  if (cropDragType !== 'move') {
    const minSize = 50  // 减小最小尺寸，适应可见区域
    if (newWidth < minSize) {
      newWidth = minSize
      // 如果加上最小尺寸后超出右边界，需要调整X位置
      if (newX + newWidth > visibleRight) {
        newX = Math.max(visibleLeft, visibleRight - newWidth)
      }
    }
    if (newHeight < minSize) {
      newHeight = minSize
      // 如果加上最小尺寸后超出下边界，需要调整Y位置
      if (newY + newHeight > visibleBottom) {
        newY = Math.max(visibleTop, visibleBottom - newHeight)
      }
    }
  }
  
  // 4. 最终边界保险检查（确保绝对不会超出可见区域）
  newX = Math.max(visibleLeft, Math.min(newX, visibleRight - newWidth))
  newY = Math.max(visibleTop, Math.min(newY, visibleBottom - newHeight))
  
  // 🔑 关键：移动操作时不修改尺寸，只调整位置
  if (cropDragType !== 'move') {
    newWidth = Math.min(newWidth, visibleRight - newX)
    newHeight = Math.min(newHeight, visibleBottom - newY)
  }
  
  // 🔑 关键：更新临时变量，避免频繁触发Vue响应式更新
  tempCropX = Math.round(newX)
  tempCropY = Math.round(newY)
  tempCropWidth = Math.round(newWidth)
  tempCropHeight = Math.round(newHeight)
  
  // 🔑 关键：如果用户手动调整裁剪框大小（非移动操作），自动切换到自由模式
  if (cropDragType !== 'move') {
    cropOptions.aspectRatio = null // 切换到自由模式
  }
}

// 裁剪拖拽结束
const handleCropMouseUp = () => {
  // 🔑 关键：拖拽结束时，将临时变量同步到响应式对象（只触发一次更新）
  cropOptions.x = tempCropX
  cropOptions.y = tempCropY
  cropOptions.width = tempCropWidth
  cropOptions.height = tempCropHeight
  
  isCropDragging = false
  cropDragType = ''
  
  // 🔑 关键：延迟重置操作标记，确保timeupdate事件不会立即干扰
  setTimeout(() => {
    isManipulatingVideo = false
  }, 100)
  
  // 🔑 关键：取消未完成的动画帧
  if (rafId) {
    cancelAnimationFrame(rafId)
    rafId = null
  }
  
  // 🔑 关键：清除缓存，释放内存
  dragDisplayArea = null
  dragVideoRect = null
  
  document.removeEventListener('mousemove', handleCropMouseMove)
  document.removeEventListener('mouseup', handleCropMouseUp)
  document.removeEventListener('touchmove', handleCropTouchMove)
  document.removeEventListener('touchend', handleCropMouseUp)
  document.removeEventListener('touchcancel', handleCropMouseUp)
}
</script>

<style scoped>
.video-converter {
  min-height: 100vh;
  width: 100vw;
  background: #f5f7fa;
  position: relative;
  overflow: hidden; /* 禁止页面下拉 */
  margin: 0;
  padding: 0;
  /* 🔑 移动端：防止双击缩放 */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.container {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 24px 2vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
  box-sizing: border-box;
  background: none;
}

.header {
  text-align: center;
  color: #1e293b;
  margin-bottom: 16px;
  animation: fadeInUp 0.8s ease-out;
  flex-shrink: 0;
}

.header h1 {
  font-size: 2rem;
  margin-bottom: 6px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: #1e293b;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.logo-icon {
  width: 240px;
  height: 180px;
  animation: fadeInUp 0.8s ease-out;
}

.subtitle {
  font-size: 0.9rem;
  color: #64748b;
  font-weight: 400;
  max-width: 600px;
  margin: 0 auto;
  line-height: 1.4;
}

.upload-section {
  background: white;
  border-radius: 12px;
  padding: 2rem;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  width: 100%;
  max-width: 100%;
  animation: fadeInUp 0.6s ease-out both;
  box-sizing: border-box;
  /* 🔑 移动端：确保内容可见 */
  overflow: visible;
  min-height: auto;
}

.upload-area {
  border: 2px dashed #d1d5db;
  border-radius: 8px;
  padding: 3rem 2rem;
  text-align: center;
  cursor: pointer;
  transition: all 0.3s;
  background: #fafbfc;
}

.upload-area:hover,
.upload-area.drag-over {
  border-color: #667eea;
  background: #f0f4ff;
}

.upload-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.upload-icon {
  font-size: 3.5rem;
  margin-bottom: 0.5rem;
}

.upload-text {
  font-size: 1.1rem;
  font-weight: 600;
  color: #374151;
}

.upload-hint {
  color: #6b7280;
  font-size: 0.875rem;
}

.select-btn {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 0.75rem 2rem;
  border-radius: 8px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s;
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}

.select-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
}

/* 旧的文件信息样式已移除 */

.change-btn {
  background: #f0f0f0;
  border: none;
  padding: 10px 25px;
  border-radius: 20px;
  cursor: pointer;
  transition: background 0.2s;
}

.change-btn:hover {
  background: #e0e0e0;
}

.tips-card {
  margin-top: 20px;
  background: #ffffff;
  border-radius: 16px;
  padding: 24px;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.05);
  width: 100%;
  box-sizing: border-box;
}

.tips-card h3 {
  margin: 0 0 12px;
  color: #111827;
  font-size: 1.1rem;
}

.tips-card ul {
  margin: 0;
  padding-left: 20px;
  color: #4b5563;
  line-height: 1.8;
}

@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes float {
  0%, 100% {
    transform: translateY(0px);
  }
  50% {
    transform: translateY(-10px);
  }
}

/* 两列工作台布局 - 与成功页面一致 */
.workbench {
  width: 100%;
  background: white;
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 20px;
  align-items: start;
}

/* 移动端：单列布局 */
@media (max-width: 768px) {
  .workbench {
    grid-template-columns: 1fr;
    gap: 16px;
    padding: 16px;
    border-radius: 12px;
    /* 🔑 移动端：确保内容可见 */
    overflow: visible;
    min-height: auto;
  }
  
  /* 🔑 移动端：确保预览区域和参数区域顺序正确 */
  .preview-section {
    order: 1;
    width: 100%;
    min-height: auto;
    overflow: visible;
  }
  
  .options-card {
    order: 2;
    width: 100%;
    min-height: auto;
    overflow: visible;
  }
  
  .tips-card {
    padding: 18px;
  }
}

/* 左侧预览区域 */
.preview-section {
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  flex-direction: column;
  width: 100%;
}

.video-header {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  margin-bottom: 12px;
  padding: 10px 14px;
  background: #f8f9fa;
  border-radius: 8px;
  flex-shrink: 0;
  width: 100%;
  box-sizing: border-box;
}

/* 文件信息样式已移除 */

.change-file-btn {
  padding: 6px 12px;
  background: white;
  border: 1px solid #ddd;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.85rem;
  transition: all 0.2s;
  flex-shrink: 0;
}

.change-file-btn:hover {
  background: #f5f5f5;
  border-color: #667eea;
  color: #667eea;
}

/* 视频预览包装器（用于裁剪模式） */
.video-preview-wrapper {
  position: relative;
  width: 100%;
  margin-bottom: 12px;
}

.video-player {
  width: 100%;
  display: block;
  background: #f5f5f5;
  border-radius: 12px;
  cursor: pointer;
  object-fit: cover; /* 填充容器，裁剪黑边 */
  /* 高度和宽高比由 JS 动态设置 */
  /* 🔑 移动端：优化触摸体验 */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

/* 隐藏视频原生控制器 */
.video-player::-webkit-media-controls {
  display: none !important;
}

.video-player::-webkit-media-controls-enclosure {
  display: none !important;
}

.video-player::-webkit-media-controls-panel {
  display: none !important;
}

.video-player::-moz-media-controls {
  display: none !important;
}

/* 裁剪覆盖层 */
.crop-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  border-radius: 12px;
}

/* 裁剪区域 */
.crop-area {
  position: absolute;
  border: 2px solid #667eea;
  background: transparent;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
  cursor: move;
  pointer-events: all;
}

.crop-area.crop-disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* 裁剪控制点 */
.crop-handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: #667eea;
  border: 2px solid white;
  border-radius: 50%;
  z-index: 10;
  cursor: pointer;
  /* 🔑 移动端：增大触摸区域 */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  /* 增大触摸区域（不影响视觉大小） */
  padding: 8px;
  margin: -8px;
}

.crop-handle.crop-handle-disabled {
  cursor: not-allowed;
  opacity: 0.4;
  pointer-events: none;
}

/* 四个角的控制点 */
.crop-handle-tl {
  top: -6px;
  left: -6px;
  cursor: nwse-resize;
}

.crop-handle-tr {
  top: -6px;
  right: -6px;
  cursor: nesw-resize;
}

.crop-handle-bl {
  bottom: -6px;
  left: -6px;
  cursor: nesw-resize;
}

.crop-handle-br {
  bottom: -6px;
  right: -6px;
  cursor: nwse-resize;
}

/* 四边的控制点 */
.crop-handle-t {
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  cursor: ns-resize;
}

.crop-handle-r {
  top: 50%;
  right: -6px;
  transform: translateY(-50%);
  cursor: ew-resize;
}

.crop-handle-b {
  bottom: -6px;
  left: 50%;
  transform: translateX(-50%);
  cursor: ns-resize;
}

.crop-handle-l {
  top: 50%;
  left: -6px;
  transform: translateY(-50%);
  cursor: ew-resize;
}

/* 裁剪控制按钮 */
.crop-controls {
  margin-bottom: 12px;
  display: flex;
  justify-content: center;
  gap: 12px;
}

.crop-btn {
  padding: 12px 24px;
  font-size: 1rem;
  border-radius: 10px;
  border: none;
  cursor: pointer;
  font-weight: 700;
  transition: all 0.3s ease;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
  position: relative;
  overflow: hidden;
}

.crop-btn:disabled {
  background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
  cursor: not-allowed;
  opacity: 0.6;
  box-shadow: none;
}

.crop-btn::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s ease;
}

.crop-btn:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
}

.crop-btn:hover::before {
  left: 100%;
}

.crop-btn.secondary {
  background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
  box-shadow: 0 6px 20px rgba(107, 114, 128, 0.4);
}

.crop-btn.secondary:hover {
  box-shadow: 0 8px 25px rgba(107, 114, 128, 0.5);
}

.crop-btn.primary {
  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
  animation: pulse-green 2s infinite;
}

.crop-btn.primary:hover {
  box-shadow: 0 8px 25px rgba(16, 185, 129, 0.6);
}

@keyframes pulse-green {
  0% {
    box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
  }
  50% {
    box-shadow: 0 8px 30px rgba(16, 185, 129, 0.6);
  }
  100% {
    box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
  }
}

.crop-actions {
  display: flex;
  gap: 12px;
}

/* 视频裁剪器样式 */
.trimmer-section {
  margin-top: 0;
  flex-shrink: 0;
  overflow: hidden;
}

.trimmer-container {
  margin-bottom: 8px;
}

.trimmer-track {
  position: relative;
  height: 80px;
  background: #2a2a2a;
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  border: 2px solid #444;
  /* 🔑 移动端：优化触摸体验 */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

.trimmer-loading {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: rgba(42, 42, 42, 0.9);
  color: white;
  font-size: 0.9rem;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(255, 255, 255, 0.2);
  border-top: 4px solid #667eea;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

.trimmer-thumbnails {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: space-between;
}

.trimmer-thumbnail {
  position: absolute;
  top: 0;
  height: 100%;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border-right: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  will-change: filter;
  transition: filter 0.15s ease;
}

.trimmer-thumbnail:hover {
  filter: brightness(1.15);
  z-index: 5;
}

.thumbnail-time {
  background: rgba(0, 0, 0, 0.7);
  color: white;
  font-size: 0.7rem;
  padding: 2px 6px;
  border-radius: 3px;
  margin-bottom: 4px;
  font-family: 'Courier New', monospace;
  opacity: 0;
  transition: opacity 0.2s;
}

.trimmer-thumbnail:hover .thumbnail-time {
  opacity: 1;
}

.trimmer-selection {
  position: absolute;
  top: 0;
  height: 100%;
  background: rgba(102, 126, 234, 0.4);
  border-top: 3px solid #667eea;
  border-bottom: 3px solid #667eea;
  box-sizing: border-box;
  z-index: 5;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
  pointer-events: auto; /* 允许拖动选区 */
  cursor: move; /* 显示移动光标 */
  transition: background 0.2s;
  /* 🔑 移动端：禁用浏览器手势，保证横向拖动 */
  touch-action: none;
}

/* 两端加厚的可拖拽区域（提高可点击/触摸命中率） */
.trimmer-edge-gutter {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 28px; /* 命中更容易 */
  z-index: 12; /* 低于手柄(15)，高于缩略图 */
  pointer-events: auto;
  background: linear-gradient(to right, rgba(102,126,234,0.12), transparent);
  touch-action: none;
}
.trimmer-edge-gutter-left {
  left: 0;
  border-right: 1px solid rgba(255,255,255,0.12);
}
.trimmer-edge-gutter-right {
  right: 0;
  border-left: 1px solid rgba(255,255,255,0.12);
  background: linear-gradient(to left, rgba(102,126,234,0.12), transparent);
}

.trimmer-selection:hover {
  background: rgba(102, 126, 234, 0.5);
}

.trimmer-selection:active {
  background: rgba(102, 126, 234, 0.6);
  cursor: grabbing;
}

.trimmer-selection.trimmer-disabled {
  cursor: not-allowed;
  opacity: 0.5;
  pointer-events: none;
}

.trimmer-handle {
  position: absolute;
  top: 0;
  width: 20px;
  height: 100%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  cursor: ew-resize;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  z-index: 15;
  box-shadow: 0 0 8px rgba(102, 126, 234, 0.6);
  pointer-events: auto;
  /* 🔑 移动端：优化触摸体验 */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  /* 增大触摸区域（不影响视觉大小） */
  padding: 0 8px;
  margin: 0 -8px;
}

.trimmer-handle:hover {
  background: linear-gradient(135deg, #5568d3 0%, #6340a0 100%);
  box-shadow: 0 0 12px rgba(102, 126, 234, 0.9);
  width: 24px;
}

.trimmer-handle.trimmer-handle-disabled {
  cursor: not-allowed;
  opacity: 0.4;
  pointer-events: none;
  background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
}

.trimmer-handle.trimmer-handle-disabled:hover {
  width: 20px;
  box-shadow: 0 0 8px rgba(102, 126, 234, 0.6);
}

.trimmer-handle-left {
  left: -10px;
  border-radius: 8px 0 0 8px;
}

.trimmer-handle-right {
  right: -10px;
  border-radius: 0 8px 8px 0;
}

.trimmer-handle-icon {
  width: 4px;
  height: 35px;
  background: white;
  border-radius: 2px;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}

.trimmer-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 10px;
  background: #f8f9fa;
  border-radius: 6px;
  margin-bottom: 8px;
  flex-wrap: wrap;
  gap: 6px;
}

.trimmer-time {
  display: flex;
  align-items: center;
  gap: 6px;
}

.time-label {
  font-size: 0.8rem;
  color: #666;
  font-weight: 500;
}

.time-value {
  font-size: 0.85rem;
  color: #333;
  font-weight: bold;
  font-family: 'Courier New', monospace;
  background: white;
  padding: 3px 6px;
  border-radius: 4px;
}

.trimmer-controls {
  display: flex;
  gap: 6px;
  justify-content: center;
}

.control-btn {
  padding: 6px 12px;
  border: none;
  border-radius: 6px;
  font-size: 0.85rem;
  cursor: pointer;
  transition: all 0.2s;
  font-weight: 500;
}

.control-btn:not(.secondary) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

.control-btn.secondary {
  background: #f0f0f0;
  color: #333;
}

.control-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.control-btn:active {
  transform: translateY(0);
}

.control-btn:disabled {
  background: #e5e7eb !important;
  color: #9ca3af !important;
  cursor: not-allowed !important;
  opacity: 0.6;
  box-shadow: none !important;
}

.control-btn:disabled:hover {
  transform: none !important;
  box-shadow: none !important;
}

.options-section h3 {
  margin-bottom: 20px;
  color: #333;
}

.option-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 14px;
  margin-bottom: 16px;
  width: 100%;
  box-sizing: border-box;
}

.option-item {
  display: flex;
  flex-direction: column;
  background: #f9f9fb;
  padding: 12px;
  border-radius: 10px;
  border: 1px solid #eef0f5;
  min-width: 0;
  overflow: hidden;
}

.option-item label {
  margin-bottom: 6px;
  color: #6b7280;
  font-size: 0.85rem;
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.option-item input {
  padding: 8px 10px;
  border: 1px solid #edf0f4;
  border-radius: 8px;
  font-size: 0.88rem;
  transition: all 0.2s;
  background: #fff;
  width: 100%;
  box-sizing: border-box;
  color: #111827;
  font-weight: 500;
  min-width: 0;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.4;
}

.option-item input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

.option-item input[type="number"]::-webkit-inner-spin-button,
.option-item input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.option-item input:focus {
  outline: none;
  border-color: #667eea;
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

.option-item input:disabled {
  background: #f3f4f6;
  color: #9ca3af;
  cursor: not-allowed;
  opacity: 0.7;
}

/* 🔑 移除复选框的边框 */
.option-item input[type="checkbox"] {
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  width: auto;
  padding: 0;
  margin: 0;
  cursor: pointer;
  background: transparent;
  appearance: checkbox;
  -webkit-appearance: checkbox;
  -moz-appearance: checkbox;
}

.option-item input[type="checkbox"]:focus,
.option-item input[type="checkbox"]:focus-visible,
.option-item input[type="checkbox"]:active {
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
}

.option-item label input[type="checkbox"] {
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
}


.convert-btn {
  width: 100%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  padding: 16px 32px;
  font-size: 1.1rem;
  font-weight: 600;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.convert-btn:disabled {
  background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
  cursor: not-allowed;
  opacity: 0.8;
}

.convert-btn:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}

.converting-content {
  display: flex;
  align-items: center;
  gap: 10px;
}

.spinner-small {
  width: 20px;
  height: 20px;
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-top-color: white;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.convert-btn:active {
  transform: translateY(-1px);
}

/* 右侧参数区域 */
.options-card { 
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 24px;
  /* 🔑 移动端：确保内容可见 */
  min-height: 0;
  overflow: visible;
}

.options-card h3 { 
  margin: 0 0 18px 0;
  color: #1f2937; 
  font-size: 1.1rem;
  font-weight: 600;
  text-align: center;
}

/* 裁剪参数样式 */
.crop-params {
  padding: 16px;
  background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
  border-radius: 12px;
  border: 2px solid #bae6fd;
  margin-bottom: 16px;
}

.crop-params h4 {
  margin: 0 0 12px 0;
  color: #0c4a6e;
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
}

.crop-presets {
  margin-top: 16px;
}

.crop-presets label {
  display: block;
  margin-bottom: 12px;
  color: #374151;
  font-size: 0.95rem;
  font-weight: 600;
  text-align: center;
}

.preset-buttons {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  max-width: 240px;
  margin: 0 auto;
}

.preset-btn {
  padding: 12px 8px;
  font-size: 0.85rem;
  border-radius: 8px;
  border: 2px solid #667eea;
  background: white;
  color: #667eea;
  cursor: pointer;
  font-weight: 700;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.2);
  min-height: 44px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.preset-btn::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent);
  transition: left 0.4s ease;
}

.preset-btn:hover {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
  border-color: #764ba2;
}

.preset-btn:hover::before {
  left: 100%;
}

.preset-btn:active {
  transform: translateY(-1px);
}

.preset-btn:disabled {
  background: #e5e7eb;
  color: #9ca3af;
  border-color: #d1d5db;
  cursor: not-allowed;
  opacity: 0.6;
  box-shadow: none;
}

.preset-btn:disabled:hover {
  transform: none;
  background: #e5e7eb;
  color: #9ca3af;
  border-color: #d1d5db;
}

/* 选中状态的快捷比例按钮 */
.preset-btn.active {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border-color: #764ba2;
  box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
  animation: glow-blue 2s infinite;
}

@keyframes glow-blue {
  0% {
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
  }
  50% {
    box-shadow: 0 8px 25px rgba(102, 126, 234, 0.7);
  }
  100% {
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
  }
}

/* 只有在选中（自由裁剪）状态下才显示紫色 */
.preset-btn-free.active {
  background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%) !important;
  color: white !important;
  border-color: #7c3aed !important;
  box-shadow: 0 6px 20px rgba(139, 92, 246, 0.6) !important;
  animation: glow-purple 2s infinite;
}

.preset-btn-free.active:hover {
  background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%) !important;
  border-color: #6d28d9 !important;
  box-shadow: 0 8px 25px rgba(139, 92, 246, 0.7) !important;
}

@keyframes glow-purple {
  0% {
    box-shadow: 0 6px 20px rgba(139, 92, 246, 0.6);
  }
  50% {
    box-shadow: 0 8px 25px rgba(139, 92, 246, 0.8);
  }
  100% {
    box-shadow: 0 6px 20px rgba(139, 92, 246, 0.6);
  }
}


@media (max-width: 1024px) {
  .container {
    padding: 40px 3vw;
  }
  
  .upload-section {
    padding: 40px 30px;
    max-width: 95%;
  }
  
  .preview-section {
    padding: 30px;
    max-width: 95%;
  }
}

/* 移动端优化 */
@media (max-width: 768px) {
  .container {
    padding: 12px 3vw;
    min-height: auto;
  }
  
  .header {
    margin-bottom: 12px;
  }
  
  .header h1 {
    font-size: 1.3rem;
    flex-direction: column;
    gap: 8px;
  }
  
  .logo-icon {
    width: 120px;
    height: 90px;
  }
  
  .subtitle {
    font-size: 0.85rem;
    padding: 0 8px;
  }
  
  .upload-section {
    padding: 16px;
    border-radius: 12px;
    /* 🔑 移动端：确保内容可见 */
    overflow: visible;
    min-height: auto;
    padding-bottom: 90px; /* 🔑 为固定按钮留出空间 */
  }
  
  /* 🔑 移动端：为固定按钮留出空间 */
  .workbench {
    padding-bottom: 90px;
  }
  
  .container {
    padding-bottom: 90px;
  }
  
  .upload-area {
    padding: 1.5rem 1rem;
    border-radius: 8px;
  }
  
  .upload-icon {
    font-size: 2.5rem;
    margin-bottom: 0.5rem;
  }
  
  .upload-text {
    font-size: 1rem;
  }
  
  .upload-hint {
    font-size: 0.8rem;
  }
  
  .select-btn {
    padding: 10px 20px;
    font-size: 0.9rem;
    margin-top: 8px;
  }
  
  .preview-section {
    padding: 0;
    width: 100%;
  }
  
  .video-preview-wrapper {
    margin-bottom: 12px;
    border-radius: 8px;
    overflow: hidden;
  }
  
  .video-player {
    height: auto;
    min-height: 200px;
    max-height: 50vh;
    border-radius: 8px;
  }
  
  .crop-overlay {
    border-radius: 8px;
  }
  
  .crop-controls {
    margin-bottom: 12px;
    flex-wrap: wrap;
    gap: 8px;
  }
  
  .crop-btn {
    padding: 10px 16px;
    font-size: 0.9rem;
    flex: 1;
    min-width: 100px;
  }
  
  .crop-actions {
    flex-wrap: wrap;
    gap: 8px;
  }
  
  .crop-actions .crop-btn {
    flex: 1;
    min-width: 80px;
  }
  
  .trimmer-section {
    margin-top: 12px;
  }
  
  .trimmer-container {
    margin-bottom: 8px;
  }
  
  .trimmer-track {
    height: 60px;
    border-radius: 6px;
    border-width: 1px;
  }
  
  .trimmer-selection {
    border-top-width: 2px;
    border-bottom-width: 2px;
  }
  
  .trimmer-handle {
    width: 16px;
  }
  
  .trimmer-handle-left {
    left: -8px;
  }
  
  .trimmer-handle-right {
    right: -8px;
  }
  
  .trimmer-handle-icon {
    width: 3px;
    height: 30px;
  }
  
  .trimmer-info {
    padding: 8px;
    gap: 6px;
    flex-wrap: wrap;
  }
  
  .trimmer-time {
    font-size: 0.75rem;
    gap: 4px;
  }
  
  .time-label {
    font-size: 0.7rem;
  }
  
  .time-value {
    font-size: 0.75rem;
    padding: 2px 5px;
  }
  
  .trimmer-controls {
    flex-direction: row;
    gap: 8px;
    flex-wrap: wrap;
  }
  
  .control-btn {
    padding: 8px 16px;
    font-size: 0.85rem;
    flex: 1;
    min-width: 100px;
  }
  
  .options-card {
    gap: 16px;
    /* 🔑 移动端：确保参数区域可见 */
    order: 2;
    width: 100%;
    min-height: auto;
    overflow: visible;
  }
  
  .options-card h3 {
    font-size: 1rem;
    margin-bottom: 12px;
    /* 🔑 移动端：确保标题可见 */
    position: relative;
    z-index: 1;
  }
  
  .crop-params {
    padding: 12px;
    margin-bottom: 12px;
  }
  
  .crop-params h4 {
    font-size: 0.9rem;
    margin-bottom: 10px;
  }
  
  .crop-presets {
    margin-top: 12px;
  }
  
  .crop-presets label {
    font-size: 0.85rem;
    margin-bottom: 10px;
  }
  
  .preset-buttons {
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    max-width: 100%;
  }
  
  .preset-btn {
    padding: 10px 6px;
    font-size: 0.8rem;
    min-height: 40px;
  }
  
  .option-row {
    grid-template-columns: 1fr;
    gap: 12px;
    margin-bottom: 12px;
  }
  
  .option-item {
    padding: 10px;
  }
  
  .option-item label {
    font-size: 0.8rem;
    margin-bottom: 6px;
  }
  
  .option-item input {
    padding: 10px 12px;
    font-size: 0.9rem;
  }
  
  .convert-btn {
    padding: 14px 24px;
    font-size: 1rem;
    border-radius: 10px;
    margin-top: 8px;
    /* 🔑 移动端：确保转换按钮可见且固定在底部 */
    position: sticky;
    bottom: 0;
    z-index: 100;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    box-shadow: 0 -4px 20px rgba(102, 126, 234, 0.3);
    width: 100%;
    /* 🔑 移动端：确保按钮足够大，易于点击 */
    min-height: 48px;
  }
  
  .video-header {
    flex-direction: column;
    gap: 10px;
    align-items: stretch;
  }
  
  .change-file-btn {
    width: 100%;
    padding: 8px 12px;
    font-size: 0.85rem;
  }
  
  .preview-toggle {
    flex-direction: column;
  }
  
  .toggle-btn {
    width: 100%;
  }
  
  .canvas-preview-container {
    min-height: 200px;
  }
  
  .preview-canvas {
    max-height: 50vh;
  }
  
  .preview-info {
    flex-direction: column;
    gap: 5px;
    font-size: 0.8rem;
  }
  
  .thumbnail-time {
    font-size: 0.55rem;
    padding: 1px 3px;
  }
}

/* 🔑 移动端：固定转换按钮（确保始终可见） */
.mobile-convert-btn-wrapper {
  display: none;
}

@media (max-width: 768px) {
  /* 🔑 移动端：隐藏桌面版转换按钮 */
  .options-card .convert-btn {
    display: none;
  }
  
  /* 🔑 移动端：显示固定转换按钮 */
  .mobile-convert-btn-wrapper {
    display: block;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 12px 16px;
    background: rgba(255, 255, 255, 0.95);
    backdrop-filter: blur(10px);
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
    border-top: 1px solid rgba(102, 126, 234, 0.1);
  }
  
  .mobile-convert-btn {
    width: 100%;
    padding: 16px 24px;
    font-size: 1.1rem;
    font-weight: 600;
    color: white;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    border-radius: 12px;
    cursor: pointer;
    transition: all 0.3s ease;
    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
    min-height: 52px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    /* 🔑 移动端：优化触摸反馈 */
    -webkit-tap-highlight-color: rgba(102, 126, 234, 0.2);
    touch-action: manipulation;
    user-select: none;
    -webkit-user-select: none;
  }
  
  .mobile-convert-btn:hover:not(:disabled) {
    background: linear-gradient(135deg, #5568d3 0%, #6340a0 100%);
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
  }
  
  .mobile-convert-btn:active:not(:disabled) {
    transform: translateY(0);
    box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
  }
  
  .mobile-convert-btn:disabled {
    background: #e5e7eb;
    color: #9ca3af;
    cursor: not-allowed;
    opacity: 0.6;
    box-shadow: none;
  }
  
  .mobile-convert-btn .converting-content {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  
  .mobile-convert-btn .spinner-small {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top-color: white;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
  }
}

/* 移动端：优化按钮触摸体验 */
@media (max-width: 768px) {
  .crop-btn,
  .control-btn,
  .preset-btn,
  .select-btn {
    /* 🔑 移动端：优化触摸反馈 */
    -webkit-tap-highlight-color: rgba(102, 126, 234, 0.2);
    touch-action: manipulation;
    /* 防止双击缩放 */
    user-select: none;
    -webkit-user-select: none;
  }
  
  /* 🔑 移动端：优化输入框 */
  .option-item input {
    /* 防止iOS自动缩放 */
    font-size: 16px !important;
    /* 优化触摸体验 */
    -webkit-tap-highlight-color: rgba(102, 126, 234, 0.1);
  }
  
  /* 🔑 移动端：优化裁剪控制点大小 */
  .crop-handle {
    width: 16px;
    height: 16px;
    border-width: 3px;
  }
  
  /* 🔑 移动端：优化时间轴手柄大小 */
  .trimmer-handle {
    width: 24px;
  }
  
  .trimmer-handle-icon {
    width: 5px;
  }
}

/* 小屏幕手机优化 */
@media (max-width: 480px) {
  .container {
    padding: 8px 2vw;
  }
  
  .tips-card {
    padding: 14px;
  }
  
  .header h1 {
    font-size: 1.1rem;
  }
  
  .logo-icon {
    width: 100px;
    height: 75px;
  }
  
  .subtitle {
    font-size: 0.8rem;
  }
  
  .upload-section {
    padding: 12px;
    /* 🔑 小屏幕：确保内容可见 */
    overflow: visible;
    min-height: auto;
    padding-bottom: 80px; /* 🔑 为固定按钮留出空间 */
  }
  
  /* 🔑 小屏幕：为固定按钮留出空间 */
  .workbench {
    padding-bottom: 80px;
  }
  
  .container {
    padding-bottom: 80px;
  }
  
  .mobile-convert-btn {
    padding: 14px 20px;
    font-size: 1rem;
    min-height: 48px;
  }
  
  .upload-area {
    padding: 1.2rem 0.8rem;
  }
  
  .upload-icon {
    font-size: 2rem;
  }
  
  .upload-text {
    font-size: 0.9rem;
  }
  
  .workbench {
    padding: 12px;
    gap: 12px;
  }
  
  .video-player {
    min-height: 180px;
    max-height: 45vh;
  }
  
  .trimmer-track {
    height: 50px;
  }
  
  .trimmer-handle {
    width: 14px;
  }
  
  .trimmer-handle-icon {
    height: 25px;
  }
  
  .trimmer-info {
    padding: 6px;
  }
  
  .trimmer-time {
    font-size: 0.7rem;
  }
  
  .time-value {
    font-size: 0.7rem;
    padding: 2px 4px;
  }
  
  .control-btn {
    padding: 8px 12px;
    font-size: 0.8rem;
    min-width: 80px;
  }
  
  .crop-btn {
    padding: 8px 12px;
    font-size: 0.85rem;
    min-width: 70px;
    /* 🔑 小屏幕：确保按钮足够大，易于触摸 */
    min-height: 44px;
  }
  
  .control-btn {
    min-height: 44px;
  }
  
  .preset-btn {
    min-height: 40px;
  }
  
  .convert-btn {
    min-height: 48px;
  }
  
  .preset-buttons {
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
  }
  
  .preset-btn {
    padding: 8px 4px;
    font-size: 0.75rem;
    min-height: 36px;
  }
  
  .option-item {
    padding: 8px;
  }
  
  .option-item label {
    font-size: 0.75rem;
  }
  
  .option-item input {
    padding: 8px 10px;
    font-size: 0.85rem;
  }
  
  .convert-btn {
    padding: 12px 20px;
    font-size: 0.95rem;
    /* 🔑 小屏幕：确保转换按钮可见 */
    position: sticky;
    bottom: 0;
    z-index: 100;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    box-shadow: 0 -4px 20px rgba(102, 126, 234, 0.3);
    width: 100%;
    min-height: 48px;
  }
  
  .options-card h3 {
    font-size: 0.95rem;
  }
  
  /* 🔑 小屏幕：确保参数区域可见 */
  .options-card {
    overflow: visible;
    padding-bottom: 20px;
  }
}
</style>


